From 632a7c862d02fe9bb5d1f96ec79065cda8fb40dc Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Wed, 5 Feb 2025 13:03:59 -0800 Subject: [PATCH 01/29] stub page for front end overview, add to nav --- src/pages/_data/navigation/mainNav.json5 | 3 +++ src/pages/build/connecting-a-front-end.md | 3 +++ src/pages/build/index.md | 7 ++++++- 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 src/pages/build/connecting-a-front-end.md diff --git a/src/pages/_data/navigation/mainNav.json5 b/src/pages/_data/navigation/mainNav.json5 index 15bd21681..4dc882fb5 100644 --- a/src/pages/_data/navigation/mainNav.json5 +++ b/src/pages/_data/navigation/mainNav.json5 @@ -37,6 +37,9 @@ { title: "Entries", url: "/build/entries/" }, { title: "Links, Paths, and Anchors", url: "/build/links-paths-and-anchors/" }, ]}, + { title: "Connecting a Front End", url: "/build/connecting-a-front-end/", children: [ + + ]}, ] }, { title: "Resources", url: "/resources/", children: [ diff --git a/src/pages/build/connecting-a-front-end.md b/src/pages/build/connecting-a-front-end.md new file mode 100644 index 000000000..4b7673a9e --- /dev/null +++ b/src/pages/build/connecting-a-front-end.md @@ -0,0 +1,3 @@ +--- +title: "Connecting a Front End" +--- \ No newline at end of file diff --git a/src/pages/build/index.md b/src/pages/build/index.md index e5428f086..25ae84c0c 100644 --- a/src/pages/build/index.md +++ b/src/pages/build/index.md @@ -40,4 +40,9 @@ Now that you've got some basic concepts and the terms we use for them, it's time * [Identifiers](/build/identifiers) --- working with hashes and other unique IDs * [Entries](/build/entries/) --- defining, creating, reading, updating, and deleting data * [Links, Paths, and Anchors](/build/links-paths-and-anchors/) --- creating relationships between data -::: \ No newline at end of file +::: + +## Connecting a front end + +::: topic-list +* [Overview](/build/connecting-a-front-end/) --- general concepts, available libraries \ No newline at end of file From 731f1810e0630bdd7dd0a86303e8e51a3a0afffd Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 7 Feb 2025 10:35:49 -0800 Subject: [PATCH 02/29] Explanation and example for connecting from vanilla JS --- src/pages/build/connecting-a-front-end.md | 55 ++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/pages/build/connecting-a-front-end.md b/src/pages/build/connecting-a-front-end.md index 4b7673a9e..c6b3aa9f3 100644 --- a/src/pages/build/connecting-a-front-end.md +++ b/src/pages/build/connecting-a-front-end.md @@ -1,3 +1,56 @@ --- title: "Connecting a Front End" ---- \ No newline at end of file +--- + +::: intro +Front ends connect to a hApp via the **application API** over a local [WebSocket](https://en.wikipedia.org/wiki/WebSocket) interface. They can call [zome functions](/build/zome-functions/), and can also do some app management tasks like **cloning** a cell and getting info about the hApp. +::: + +## Where a front end runs + +The most important thing to remember about a hApp is that it runs in the Holochain conductor **on each of the devices of the agents themselves**, whether those agents are humans, bots, or headless services. Holochain itself is just an engine for a hApp's back end, so it exposes the hApp's API (its zome functions) and lets front ends connect to the hApp via a WebSocket interface. + +This interface is **only exposed to processes on the local device**, not to external network adapters. This helps prevent unauthorized agents from accessing the hApp. It also means the front end must be distributed with the hApp and a Holochain runtime. + +Some Holochain runtimes bundle the conductor and a front end host that serves your HTML/JavaScript-based runtimes. Take a look at the [Packaging and Distribution](/get-started/4-packaging-and-distribution/) page from the Get Started guide for the choices. For this build guide, we'll be using `hc-launch`, a runtime used for testing your hApp, which comes with the Holonix dev environment. + +## Front-end libraries + +Holochain provides client libraries for [JavaScript](https://github.com/holochain/holochain-client-js) and [Rust](https://github.com/holochain/holochain-client-rust). The scaffolding tool generates JavaScript-based UIs that are meant to be served as a single-page app, so we'll focus on JavaScript for this documentation --- or more specifically TypeScript, because that's what the client library is written in. + +## Connect to a hApp with the JavaScript client + +You connect to the application API with the client's [`AppWebsocket.connect`](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.connect.md) method, which returns a Promise<[AppWebsocket](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.md)>. + +If you've been using the scaffolding tool with the UI option, all the code to establish a connection to a hApp is already written for you. You can get it to build reasonable bootstrap code for Lit, React, Svelte, and Vue that makes the client available to child components once it's connected to a hApp. + +But for now, we're going to give you a simple TypeScript example, inspired by the `ui/index.html` file from a hApp scaffolded with the `vanilla` UI option. We'll call on the function in all the following examples, so that we don't have to create a connection every time. + +```typescript +import { AppWebsocket, HolochainError } from '@holochain/client'; + +{ + let client = AppWebsocket | undefined; + + async function connect() { + if (client === undefined) { + client = await AppWebsocket.connect(); + console.log("Connected to Holochain! hApp ID is ${client.installedAppId}"); + } + return client; + } +} + +connect().catch((error: HolochainError) => console.error(`Connection failure, name ${error.name}, message ${error.message}`)); +``` + +You'll notice that you don't have to pass a connection URI to the client. That's because, at time of writing, all Holochain runtimes that serve a web-based UI will inject a constant into the page that contains the URI, and the client will look for that value. So the scaffolding tool expects you'll be distributing your hApp with one of these runtimes. Check out the [`AppWebsocket.connect` documentation](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.connect.md) if you're building a front end that runs separately from a Holochain runtime. + +## Reference + +* [`AppWebsocket`](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.md) +* [`AppWebsocket.connect`](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.connect.md) + +## Further reading + +* [Core Concepts: Application Architecture](/concepts/2_application_architecture/) \ No newline at end of file From e0c0142b9ca1521ba12aef2d1c1542ea18ef4523 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 7 Feb 2025 10:43:44 -0800 Subject: [PATCH 03/29] rename connection function --- .../build/callbacks-and-lifecycle-hooks.md | 24 +++++++++---------- src/pages/build/connecting-a-front-end.md | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/pages/build/callbacks-and-lifecycle-hooks.md b/src/pages/build/callbacks-and-lifecycle-hooks.md index b3b6b2803..c2235efde 100644 --- a/src/pages/build/callbacks-and-lifecycle-hooks.md +++ b/src/pages/build/callbacks-and-lifecycle-hooks.md @@ -160,9 +160,13 @@ use foo_integrity::LinkTypes; use hdk::prelude::*; // We're creating this type for both remote signals to other peers and local -// signals to the UI. +// signals to the UI. Your app might have different kinds of signals for each, +// so you're free to define separate types for local vs remote. +// We recommend making your signal type an enum, so your hApp can define +// different kinds of signals. #[derive(Serialize, Deserialize, Debug)] -#[serde(tag = "type")] +// It's helpful to match the way Holochain serializes its own enums. +#[serde(tag = "type", content = "value", rename_all = "snake_case")] enum Signal { Heartbeat(AgentPubKey), } @@ -227,7 +231,8 @@ use movie_integrity::{EntryTypes, Movie, MovieLoan, UnitEntryTypes}; use hdk::prelude::*; #[derive(Clone, Serialize, Deserialize, Debug)] -pub enum RemoteSignal { +#[serde(tag = "type", content = "value", rename_all = "snake_case")] +pub enum Signal { MovieLoanHasBeenCreatedForYou(ActionHash), } @@ -238,7 +243,7 @@ pub fn post_commit(actions: Vec) { if let Action::Create(_) = action.action() { if let Ok(movie_loan) = get_movie_loan(action.action_address().clone()) { send_remote_signal( - RemoteSignal::MovieLoanHasBeenCreatedForYou(action.action_address().clone()), + Signal::MovieLoanHasBeenCreatedForYou(action.action_address().clone()), vec![movie_loan.lent_to] ).ok(); // suppress warning about unhandled `Result` } @@ -246,17 +251,12 @@ pub fn post_commit(actions: Vec) { } } -#[derive(Serialize, Deserialize, Debug)] -enum LocalSignal { - NewMovieLoan(MovieLoan), -} - #[hdk_extern] -pub fn recv_remote_signal(payload: RemoteSignal) -> ExternResult<()> { - if let RemoteSignal::MovieLoanHasBeenCreatedForYou(action_hash) = payload { +pub fn recv_remote_signal(payload: Signal) -> ExternResult<()> { + if let Signal::MovieLoanHasBeenCreatedForYou(action_hash) = payload { let movie_loan = get_movie_loan(action_hash)?; // Send the new movie loan data to the borrower's UI! - emit_signal(LocalSignal::NewMovieLoan(movie_loan))?; + emit_signal(Signal::MovieLoanHasBeenCreatedForYou(movie_loan))?; } Ok(()) } diff --git a/src/pages/build/connecting-a-front-end.md b/src/pages/build/connecting-a-front-end.md index c6b3aa9f3..8f03ade5f 100644 --- a/src/pages/build/connecting-a-front-end.md +++ b/src/pages/build/connecting-a-front-end.md @@ -32,7 +32,7 @@ import { AppWebsocket, HolochainError } from '@holochain/client'; { let client = AppWebsocket | undefined; - async function connect() { + async function getHolochainClient() { if (client === undefined) { client = await AppWebsocket.connect(); console.log("Connected to Holochain! hApp ID is ${client.installedAppId}"); @@ -41,7 +41,7 @@ import { AppWebsocket, HolochainError } from '@holochain/client'; } } -connect().catch((error: HolochainError) => console.error(`Connection failure, name ${error.name}, message ${error.message}`)); +getHolochainClient().catch((error: HolochainError) => console.error(`Connection failure, name ${error.name}, message ${error.message}`)); ``` You'll notice that you don't have to pass a connection URI to the client. That's because, at time of writing, all Holochain runtimes that serve a web-based UI will inject a constant into the page that contains the URI, and the client will look for that value. So the scaffolding tool expects you'll be distributing your hApp with one of these runtimes. Check out the [`AppWebsocket.connect` documentation](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.connect.md) if you're building a front end that runs separately from a Holochain runtime. From 9199a7d0a8c2178cb2e414baab376cc6c6aacaa0 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 7 Feb 2025 10:45:54 -0800 Subject: [PATCH 04/29] fix unclosed formatting block --- src/pages/build/index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/build/index.md b/src/pages/build/index.md index 25ae84c0c..e1f484328 100644 --- a/src/pages/build/index.md +++ b/src/pages/build/index.md @@ -45,4 +45,5 @@ Now that you've got some basic concepts and the terms we use for them, it's time ## Connecting a front end ::: topic-list -* [Overview](/build/connecting-a-front-end/) --- general concepts, available libraries \ No newline at end of file +* [Overview](/build/connecting-a-front-end/) --- general concepts, available libraries +::: \ No newline at end of file From a9dcddb41949e68c3faf081fd843b16348473695 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 7 Feb 2025 11:09:28 -0800 Subject: [PATCH 05/29] TOC for connecting everything together section --- src/pages/_data/navigation/mainNav.json5 | 4 ++-- src/pages/build/index.md | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/pages/_data/navigation/mainNav.json5 b/src/pages/_data/navigation/mainNav.json5 index 4dc882fb5..392337e75 100644 --- a/src/pages/_data/navigation/mainNav.json5 +++ b/src/pages/_data/navigation/mainNav.json5 @@ -37,8 +37,8 @@ { title: "Entries", url: "/build/entries/" }, { title: "Links, Paths, and Anchors", url: "/build/links-paths-and-anchors/" }, ]}, - { title: "Connecting a Front End", url: "/build/connecting-a-front-end/", children: [ - + { title: "Connecting Everything Together", url: "/build/connecting-everything-together/", children: [ + { title: "Connecting a Front End", url: "/build/connecting-a-front-end/" }, ]}, ] }, diff --git a/src/pages/build/index.md b/src/pages/build/index.md index 25ae84c0c..a42835609 100644 --- a/src/pages/build/index.md +++ b/src/pages/build/index.md @@ -42,7 +42,11 @@ Now that you've got some basic concepts and the terms we use for them, it's time * [Links, Paths, and Anchors](/build/links-paths-and-anchors/) --- creating relationships between data ::: -## Connecting a front end +## Connecting everything together ::: topic-list -* [Overview](/build/connecting-a-front-end/) --- general concepts, available libraries \ No newline at end of file +* [Overview](/build/connecting-everything-together/) --- zome calls, capabilities, and signals +* [Connecting a front end](/build/connecting-a-front-end/) --- establishing a WebSocket connection from JavaScript +* Calling a zome function (coming soon) --- examples for front ends, cell-to-cell, and agent-to-agent +* Capabilities (coming soon) --- how to manage access to a cell's zome functions +* Working with signals (coming soon) --- receiving notifications from cells \ No newline at end of file From 9ee367b2f18133d8d619499bd46bce4491c7b611 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 7 Feb 2025 12:42:45 -0800 Subject: [PATCH 06/29] connecting everything together overview page --- src/pages/build/application-structure.md | 4 +- .../build/connecting-everything-together.md | 51 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 src/pages/build/connecting-everything-together.md diff --git a/src/pages/build/application-structure.md b/src/pages/build/application-structure.md index 160311ca9..18a8e0165 100644 --- a/src/pages/build/application-structure.md +++ b/src/pages/build/application-structure.md @@ -65,7 +65,7 @@ Because each DNA has its own separate peer network and data store, you can use t [Read more on the DNAs page](/build/dnas/). -### hApp +### hApp {#happ} One or more DNAs come together in a **hApp** (Holochain app). Each DNA fills a named **role** in the hApp, and you can think of it like a [microservice](https://en.wikipedia.org/wiki/Microservices). @@ -78,7 +78,7 @@ The hApp can specify two provisioning strategies for its DNAs: A hApp can optionally include a web-based UI that supporting Holochain runtimes can serve to the user. -!!! info A hApp always runs locally +!!! info A hApp always runs locally {#local} The big difference with peer-to-peer stacks like Holochain is that **all the code** --- both the back end and the front end --- **runs on the devices of the participants themselves**. diff --git a/src/pages/build/connecting-everything-together.md b/src/pages/build/connecting-everything-together.md new file mode 100644 index 000000000..778944785 --- /dev/null +++ b/src/pages/build/connecting-everything-together.md @@ -0,0 +1,51 @@ +--- +title: "Connecting Everything Together" +--- + +::: topic-list +### In this section {data-no-toc} + +* Connecting everything together (this page) + * [Connecting a front end](/build/connecting-a-front-end/) --- establishing a WebSocket connection from JavaScript + * Calling a zome function (coming soon) --- examples for front ends, cell-to-cell, and agent-to-agent + * Capabilities (coming soon) --- how to manage access to a cell's zome functions + * Working with signals (coming soon) --- receiving notifications from cells +::: + +::: intro +Your hApp back end's public interface consists of all the [**zome functions**](/build/zome-functions/) of all the [**cells**](/concepts/2_application_architecture/#cell) instantiated from all the [**DNAs**](/build/dnas/) that fill the hApp's [**roles**](/build/application-structure/#happ). It's accessible to locally running processes and to network peers, and is secured by a form of **capability-based security**, adapted for peer-to-peer applications. + +The back end can also send out [**signals**](/concepts/9_signals/) that can be received either by UIs or remote peers. +::: + +## What processes can connect to a hApp? + +It's worth mentioning again that **all the components of a hApp backend [run on the devices of the individual agents](/build/application-structure/#local)** as cells representing those agents' autonomy --- their capacity to write their own data to their own source chains. + +With that in mind, these are the kinds of processes that can make calls to a cell's zome functions: + +1. another coordinator zome in the same cell +2. another cell in the same hApp on the agent's device +3. an external client, such as a UI, script, or system service, on the agent's device +4. another peer in the same network as the cell (that is, sharing the same DNA hash) + +Of these, only number 3, an external client, can listen to local signals emitted by a zome function. (There's another kind of signal, a remote signal, that's sent between peers in a network. It's actually just a zome function with a special name.) + +### How does a process connect? + +For cases 1 and 2 above, the Holochain conductor handles inter-process communication between cells. For case 3, Holochain exposes a WebSocket interface for clients to call. And for case 4, the two peers' conductors handle the zome call over the network. + +## Securing zome functions against unauthorized access + +An agent naturally doesn't want any remote peer calling any of their zome functions, and even local processes should be restricted in case of poorly written DNAs or malware processes on the machine. Holochain uses a modified form of [capability-based security](https://en.wikipedia.org/wiki/Capability-based_security) to secure zome function access. + +Capability-based security, in short, says that you should never give out direct access to a resource such as a file or network connection. Instead you mediate access to that resource and give out 'handles' that represent a set of privileges. Holochain expands on this idea by adding the ability to restrict resource access to a certain group of agents, represented by their private keys. + +This is a complex topic, so we're going to write a separate page about it soon. + +## Sending signals for reactive, event-based programming + +Zome functions can send out signals, either locally to front ends or remotely to other agents in the same network. This lets you write programs that react to activity rather than having to poll a function for updates. We'll write more about this soon as well! + +## Further reading + From f56c9ae03dc5043d8b1f197df17582c12d0941ff Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 7 Feb 2025 12:59:54 -0800 Subject: [PATCH 07/29] add bibliograpy to connecting page --- src/pages/build/connecting-everything-together.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/build/connecting-everything-together.md b/src/pages/build/connecting-everything-together.md index 778944785..355f13de9 100644 --- a/src/pages/build/connecting-everything-together.md +++ b/src/pages/build/connecting-everything-together.md @@ -49,3 +49,6 @@ Zome functions can send out signals, either locally to front ends or remotely to ## Further reading +* [Core Concepts: Application Architecture](/concepts/2_application_architecture/) +* [Core Concepts: Calls and Capabilities](/concepts/8_calls_capabilities/) +* [Core Concepts: Signals](/concepts/9_signals/) \ No newline at end of file From 1ab7b9e4f41f92ceac0240db5852304c038de556 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 7 Feb 2025 13:02:20 -0800 Subject: [PATCH 08/29] mention signals in connecting page --- src/pages/build/connecting-a-front-end.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/build/connecting-a-front-end.md b/src/pages/build/connecting-a-front-end.md index 8f03ade5f..908482ad1 100644 --- a/src/pages/build/connecting-a-front-end.md +++ b/src/pages/build/connecting-a-front-end.md @@ -3,7 +3,7 @@ title: "Connecting a Front End" --- ::: intro -Front ends connect to a hApp via the **application API** over a local [WebSocket](https://en.wikipedia.org/wiki/WebSocket) interface. They can call [zome functions](/build/zome-functions/), and can also do some app management tasks like **cloning** a cell and getting info about the hApp. +Front ends connect to a hApp via the **application API** over a local [WebSocket](https://en.wikipedia.org/wiki/WebSocket) interface. They can call [**zome functions**](/build/zome-functions/) and listen to [**signals**](/concepts/9_signals/), and can also do some app management tasks like **cloning** a cell and getting info about the hApp. ::: ## Where a front end runs From 3e42cb6be09c03af57e7793f0ed58fe6c22b2f34 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 7 Feb 2025 13:06:54 -0800 Subject: [PATCH 09/29] mention signals in connecting page --- src/pages/build/connecting-a-front-end.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/build/connecting-a-front-end.md b/src/pages/build/connecting-a-front-end.md index 8f03ade5f..908482ad1 100644 --- a/src/pages/build/connecting-a-front-end.md +++ b/src/pages/build/connecting-a-front-end.md @@ -3,7 +3,7 @@ title: "Connecting a Front End" --- ::: intro -Front ends connect to a hApp via the **application API** over a local [WebSocket](https://en.wikipedia.org/wiki/WebSocket) interface. They can call [zome functions](/build/zome-functions/), and can also do some app management tasks like **cloning** a cell and getting info about the hApp. +Front ends connect to a hApp via the **application API** over a local [WebSocket](https://en.wikipedia.org/wiki/WebSocket) interface. They can call [**zome functions**](/build/zome-functions/) and listen to [**signals**](/concepts/9_signals/), and can also do some app management tasks like **cloning** a cell and getting info about the hApp. ::: ## Where a front end runs From 76b2d23babe734e8790f4e32100fd23288fe576b Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 7 Feb 2025 13:53:48 -0800 Subject: [PATCH 10/29] call a zome function page --- src/pages/_data/navigation/mainNav.json5 | 1 + src/pages/build/calling-a-zome-function.md | 134 ++++++++++++++++++ src/pages/build/connecting-a-front-end.md | 2 +- .../build/connecting-everything-together.md | 2 +- src/pages/build/index.md | 2 +- src/pages/concepts/8_calls_capabilities.md | 2 +- 6 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 src/pages/build/calling-a-zome-function.md diff --git a/src/pages/_data/navigation/mainNav.json5 b/src/pages/_data/navigation/mainNav.json5 index 392337e75..5f0857a5c 100644 --- a/src/pages/_data/navigation/mainNav.json5 +++ b/src/pages/_data/navigation/mainNav.json5 @@ -39,6 +39,7 @@ ]}, { title: "Connecting Everything Together", url: "/build/connecting-everything-together/", children: [ { title: "Connecting a Front End", url: "/build/connecting-a-front-end/" }, + { title: "Calling a Zome Function", url: "/build/calling-a-zome-function" }, ]}, ] }, diff --git a/src/pages/build/calling-a-zome-function.md b/src/pages/build/calling-a-zome-function.md new file mode 100644 index 000000000..65034bcd1 --- /dev/null +++ b/src/pages/build/calling-a-zome-function.md @@ -0,0 +1,134 @@ +--- +title: "Calling a zome function" +--- + +::: intro +You can call a zome function from [various local and remote processes](/build/connecting-everything-together/#what-processes-can-connect-to-a-happ) and secure them with capability tokens, letting you implement secure interop easily. +::: + +## Call a zome function from a front end + +To call a zome function, use [`AppWebsocket.prototype.callZome`](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.callzome.md). It takes a [`CallZomeRequest`](https://github.com/holochain/holochain-client-js/blob/main/docs/client.callzomerequest.md) and an optional timeout. + +This example gets all the movies by a given director, then logs their titles to the console. You'll need the [`getHolochainClient` example](/build/connecting-a-front-end/#connect-to-a-happ-with-the-javascript-client) from the Connecting to a Front End page. + +```typescript +import { EntryHash } from '@holochain/client'; + +class Movie { + title: string, + director_hash: EntryHash, + imdb_id: string | null, + release_date: Date, + box_office_revenue: Number, +} + +async function logMoviesBySergioLeone() { + // Use the `getHolochainClient` function from /build/connecting-a-front-end/ + const client = await getHolochainClient(); + const movies: Array = await client.callZome({ + role_name: "movies", + zome_name: "movies", + fn_name: "get_movies_by_director", + // This should match the input payload struct for the zome function. + payload: { + director_hash: [ /* bytes of Sergio Leone director entry's hash */] + } + }); + for (let movie in movies) { + console.log(movie.title); + } +} +``` + +The client handles errors (both `ExternResult::Err(_)` errors from the zome function and other errors from Holochain itself) and deserializes the MessagePack return value for you. It also takes care of capability security for you. + +## Call a zome function from another zome in the same hApp + +Coordinator zomes can't call each other's functions directly; instead they do it via the [`hdk::prelude::call`](https://docs.rs/hdk/latest/hdk/p2p/fn.call.html) host function. + +This example calls a function `foo` in another zome called `bar` _in the same cell_: + +```rust +use hdk::prelude::*; +// It's useful to put zome function input and output types in a separate crate +// from the zome itself, so they can be imported by zomes that call them. +use bar_types::*; + +let foo_input: FooInput { + age: 57, + height: 173, +} +let response = call( + // This indicates that `bar` is in the same cell. + CallTargetCell::Local, + "bar", + "foo", + // This is a capability secret -- we'll explain why it's not needed. + None, + foo_input; +)? +match response { + // Do something with the return value. + Ok(output) => { + let foo_output: FooOutput = output.try_into()?; + debug!("Got a response; it said {}", foo_output.greeting); + } + _ => error!("Something bad happened"), +} +``` + +This example calls that same function _if it's in a different cell_ whose role name is `qux`: + +```rust +use hdk::prelude::*; +use bar_types::*; + +// Construct foo_input here. +let response = call( + // This indicates that `bar` is in another cell. + CallTargetCell::OtherRole("qux"), + "bar", + "foo", + // This is a capability secret -- we'll explain why it's not needed. + None, + foo_input; +)? +// Do something with response. +``` + +These cases don't need to worry about capability security either, because they're covered by a special grant called the [**author grant**](/concepts/8_calls_capabilities/#author-grant). It permits calls made by any caller with the same public key as the callee cell's owner. + +## Call a zome function from another agent in the network + +If two agents have cells running the same DNA --- that is, they're part of the same network --- they can call each other's zome functions _in the same DNA_ using [`hdk::prelude::call_remote`](https://docs.rs/hdk/latest/hdk/p2p/fn.call_remote.html). + +!!! info A remote cell might not be running the same coordinator zomes +Holochain allows agents to add and remove coordinator zomes from a DNA. This permits upgrading and customization. But it also means that the zomes and functions that you _think_ are on the other end might not actually be there. +!!! + +This example calls a function _in the same coordinator zome_ (or at least one with the same name) in a remote agent's cell. It assumes that the remote agent has granted access to their `foo` function with an [**unrestricted grant**](/concepts/8_calls_capabilities/#unrestricted), which doesn't require a capability secret. + +```rust +use hdk::prelude::*; + +let foo_input: FooInput { + age: 57, + height: 173, +} +let bob_public_key: AgentPubKey = vec![/* bytes of remote agent's key */]; +let response = call_remote( + bob_public_key, + // Get this zome's name from the host. + zome_info()?.name, + "foo", + // No capability secret needed for unrestricted functions. + None, + foo_input, +); +match response { + Ok(_) => debug!("it worked"), + Unauthorized(_, _, _, _, _) => debug!("I wasn't allowed to call this function on Bob's device"), + _ => "Something unexpected happened", +} +``` \ No newline at end of file diff --git a/src/pages/build/connecting-a-front-end.md b/src/pages/build/connecting-a-front-end.md index 908482ad1..0b717ff33 100644 --- a/src/pages/build/connecting-a-front-end.md +++ b/src/pages/build/connecting-a-front-end.md @@ -18,7 +18,7 @@ Some Holochain runtimes bundle the conductor and a front end host that serves yo Holochain provides client libraries for [JavaScript](https://github.com/holochain/holochain-client-js) and [Rust](https://github.com/holochain/holochain-client-rust). The scaffolding tool generates JavaScript-based UIs that are meant to be served as a single-page app, so we'll focus on JavaScript for this documentation --- or more specifically TypeScript, because that's what the client library is written in. -## Connect to a hApp with the JavaScript client +## Connect to a hApp with the JavaScript client {#connect-to-a-happ-with-the-javascript-client} You connect to the application API with the client's [`AppWebsocket.connect`](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.connect.md) method, which returns a Promise<[AppWebsocket](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.md)>. diff --git a/src/pages/build/connecting-everything-together.md b/src/pages/build/connecting-everything-together.md index 355f13de9..5ba845f78 100644 --- a/src/pages/build/connecting-everything-together.md +++ b/src/pages/build/connecting-everything-together.md @@ -18,7 +18,7 @@ Your hApp back end's public interface consists of all the [**zome functions**](/ The back end can also send out [**signals**](/concepts/9_signals/) that can be received either by UIs or remote peers. ::: -## What processes can connect to a hApp? +## What processes can connect to a hApp? {#what-processes-can-connect-to-a-happ} It's worth mentioning again that **all the components of a hApp backend [run on the devices of the individual agents](/build/application-structure/#local)** as cells representing those agents' autonomy --- their capacity to write their own data to their own source chains. diff --git a/src/pages/build/index.md b/src/pages/build/index.md index 81e2081ce..15b725040 100644 --- a/src/pages/build/index.md +++ b/src/pages/build/index.md @@ -47,6 +47,6 @@ Now that you've got some basic concepts and the terms we use for them, it's time ::: topic-list * [Overview](/build/connecting-everything-together/) --- zome calls, capabilities, and signals * [Connecting a front end](/build/connecting-a-front-end/) --- establishing a WebSocket connection from JavaScript -* Calling a zome function (coming soon) --- examples for front ends, cell-to-cell, and agent-to-agent +* [Calling a zome function](/build/calling-a-zome-function/) --- examples for front ends, cell-to-cell, and agent-to-agent * Capabilities (coming soon) --- how to manage access to a cell's zome functions * Working with signals (coming soon) --- receiving notifications from cells diff --git a/src/pages/concepts/8_calls_capabilities.md b/src/pages/concepts/8_calls_capabilities.md index da567d465..0e19cdb52 100644 --- a/src/pages/concepts/8_calls_capabilities.md +++ b/src/pages/concepts/8_calls_capabilities.md @@ -67,7 +67,7 @@ Holochain uses a variation of [capability-based security](https://wikipedia.org/ ![](/assets/img/concepts/8.6-unrestricted-capability.png){.sz80p} {.center} -An **unrestricted** capability lets anybody call a function without producing a token. +An **unrestricted** capability lets anybody call a function without producing a token. {#unrestricted} ![](/assets/img/concepts/8.7-transferrable-capability.png){.sz80p} {.center} From 2f9c1ecd245a62d6f7ecb1aced6eb42dcfe77f4f Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 7 Feb 2025 13:57:32 -0800 Subject: [PATCH 11/29] bibliography for zome calling page --- src/pages/build/calling-a-zome-function.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/pages/build/calling-a-zome-function.md b/src/pages/build/calling-a-zome-function.md index 65034bcd1..4cfb15c05 100644 --- a/src/pages/build/calling-a-zome-function.md +++ b/src/pages/build/calling-a-zome-function.md @@ -131,4 +131,16 @@ match response { Unauthorized(_, _, _, _, _) => debug!("I wasn't allowed to call this function on Bob's device"), _ => "Something unexpected happened", } -``` \ No newline at end of file +``` + +## Reference + +* [`AppWebsocket.prototype.callZome`](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.callzome.md) +* [`CallZomeRequest`](https://github.com/holochain/holochain-client-js/blob/main/docs/client.callzomerequest.md) +* [`hdk::p2p::call`](https://docs.rs/hdk/latest/hdk/p2p/fn.call.html) +* [`holochain_zome_types::call::CallTargetCell`](https://docs.rs/holochain_zome_types/latest/holochain_zome_types/call/enum.CallTargetCell.html)* [`holochain_zome_types::zome_io::ZomeCallResponse`](https://docs.rs/holochain_zome_types/latest/holochain_zome_types/zome_io/enum.ZomeCallResponse.html) +* [`hdk::info::zome_info`](https://docs.rs/hdk/latest/hdk/info/fn.zome_info.html) + +## Further reading + +* [Core Concepts: Calls and Capabilities](/concepts/8_calls_capabilities) \ No newline at end of file From 72cb320ee4ccf15854618bb3bab9621f5d7c1edd Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 7 Feb 2025 14:10:48 -0800 Subject: [PATCH 12/29] fix zome code examples for call and call_remote --- src/pages/build/calling-a-zome-function.md | 130 ++++++++++++--------- 1 file changed, 75 insertions(+), 55 deletions(-) diff --git a/src/pages/build/calling-a-zome-function.md b/src/pages/build/calling-a-zome-function.md index 4cfb15c05..41ec8a78c 100644 --- a/src/pages/build/calling-a-zome-function.md +++ b/src/pages/build/calling-a-zome-function.md @@ -47,54 +47,62 @@ The client handles errors (both `ExternResult::Err(_)` errors from the zome func Coordinator zomes can't call each other's functions directly; instead they do it via the [`hdk::prelude::call`](https://docs.rs/hdk/latest/hdk/p2p/fn.call.html) host function. -This example calls a function `foo` in another zome called `bar` _in the same cell_: +This example calls a function `get_movies_by_director` in another zome called `movies` _in the same cell_: ```rust use hdk::prelude::*; // It's useful to put zome function input and output types in a separate crate // from the zome itself, so they can be imported by zomes that call them. -use bar_types::*; - -let foo_input: FooInput { - age: 57, - height: 173, -} -let response = call( - // This indicates that `bar` is in the same cell. - CallTargetCell::Local, - "bar", - "foo", - // This is a capability secret -- we'll explain why it's not needed. - None, - foo_input; -)? -match response { - // Do something with the return value. - Ok(output) => { - let foo_output: FooOutput = output.try_into()?; - debug!("Got a response; it said {}", foo_output.greeting); +use movies_types::*; + +fn get_movies_by_sergio_leone() -> ExternResult> { + let input: GetMovieByDirectorInput = { + director_hash: [/* bytes of Sergio Leone director entry's hash */] + }; + let response = call( + // This indicates that `bar` is in the same cell. + CallTargetCell::Local, + "movies", + "get_movies_by_director", + // This is a capability secret -- we'll explain why it's not needed. + None, + input; + )?; + match response { + ZomeCallResponse::Ok(output) => output + .try_into() + .map_err(|e| wasm_error!(e.to_string()))?, + _ => Err(wasm_error!("Something bad happened")), } - _ => error!("Something bad happened"), } ``` -This example calls that same function _if it's in a different cell_ whose role name is `qux`: +This example calls that same function _if it's in a different cell_ whose role name is `movies`: ```rust use hdk::prelude::*; -use bar_types::*; - -// Construct foo_input here. -let response = call( - // This indicates that `bar` is in another cell. - CallTargetCell::OtherRole("qux"), - "bar", - "foo", - // This is a capability secret -- we'll explain why it's not needed. - None, - foo_input; -)? -// Do something with response. +use movies_types::*; + +fn get_movies_by_sergio_leone_from_movies_cell() -> ExternResult> { + let input: GetMovieByDirectorInput = { + director_hash: [/* bytes of Sergio Leone director entry's hash */] + }; + let response = call( + // Address the cell by its role name. + // You can also address it by its cell ID. + CallTargetCell::OtherRole("movies"), + "movies", + "get_movies_by_director", + None, + input; + )?; + match response { + ZomeCallResponse::Ok(output) => output + .try_into() + .map_err(|e| wasm_error!(e.to_string()))?, + _ => Err(wasm_error!("Something bad happened")), + } +} ``` These cases don't need to worry about capability security either, because they're covered by a special grant called the [**author grant**](/concepts/8_calls_capabilities/#author-grant). It permits calls made by any caller with the same public key as the callee cell's owner. @@ -107,29 +115,41 @@ If two agents have cells running the same DNA --- that is, they're part of the s Holochain allows agents to add and remove coordinator zomes from a DNA. This permits upgrading and customization. But it also means that the zomes and functions that you _think_ are on the other end might not actually be there. !!! -This example calls a function _in the same coordinator zome_ (or at least one with the same name) in a remote agent's cell. It assumes that the remote agent has granted access to their `foo` function with an [**unrestricted grant**](/concepts/8_calls_capabilities/#unrestricted), which doesn't require a capability secret. +This example calls a function _in the same coordinator zome_ (or at least one with the same name) in a remote agent's cell. It assumes that the remote agent has granted access to their `get_movies_by_director` function with an [**unrestricted grant**](/concepts/8_calls_capabilities/#unrestricted), which doesn't require a capability secret. ```rust use hdk::prelude::*; -let foo_input: FooInput { - age: 57, - height: 173, -} -let bob_public_key: AgentPubKey = vec![/* bytes of remote agent's key */]; -let response = call_remote( - bob_public_key, - // Get this zome's name from the host. - zome_info()?.name, - "foo", - // No capability secret needed for unrestricted functions. - None, - foo_input, -); -match response { - Ok(_) => debug!("it worked"), - Unauthorized(_, _, _, _, _) => debug!("I wasn't allowed to call this function on Bob's device"), - _ => "Something unexpected happened", +fn get_movies_by_sergio_leone_remote() -> ExternResult> { + let input: GetMovieByDirectorInput = { + director_hash: [/* bytes of Sergio Leone director entry's hash */] + }; + let bob_public_key: AgentPubKey = vec![/* bytes of remote agent's key */]; + let response = call_remote( + bob_public_key, + // Get this zome's name from the host. + zome_info()?.name, + "foo", + // No capability secret needed for unrestricted functions. + None, + foo_input, + ); + let response = call( + // Address the cell by its role name. + // You can also address it by its cell ID. + CallTargetCell::OtherRole("movies"), + "movies", + "get_movies_by_director", + None, + input; + )?; + match response { + ZomeCallResponse::Ok(output) => output + .try_into() + .map_err(|e| wasm_error!(e.to_string()))?, + ZomeCallResponse::Unauthorized(_, _, _, _, _) => Err(wasm_error!("I wasn't allowed to call this function on Bob's device")), + _ => Err(wasm_error!("Something bad happened")), + } } ``` From c0e555b02349ba7aa0de311935f7a01ae0996627 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 7 Feb 2025 14:55:39 -0800 Subject: [PATCH 13/29] turn conn getter into IIFE for compatibility --- src/pages/build/connecting-a-front-end.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/build/connecting-a-front-end.md b/src/pages/build/connecting-a-front-end.md index 908482ad1..ce2c714c6 100644 --- a/src/pages/build/connecting-a-front-end.md +++ b/src/pages/build/connecting-a-front-end.md @@ -29,17 +29,17 @@ But for now, we're going to give you a simple TypeScript example, inspired by th ```typescript import { AppWebsocket, HolochainError } from '@holochain/client'; -{ - let client = AppWebsocket | undefined; +const getHolochainClient = (() => { + let client: AppWebsocket | undefined; - async function getHolochainClient() { + return async () => { if (client === undefined) { client = await AppWebsocket.connect(); console.log("Connected to Holochain! hApp ID is ${client.installedAppId}"); } return client; - } -} + }; +})(); getHolochainClient().catch((error: HolochainError) => console.error(`Connection failure, name ${error.name}, message ${error.message}`)); ``` From c05a4887b97792b460127afb40b24be50cf4cbc8 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 11 Feb 2025 09:42:01 -0800 Subject: [PATCH 14/29] fix JS and Rust examples in zome calls page --- src/pages/build/calling-a-zome-function.md | 60 ++++++++-------------- 1 file changed, 21 insertions(+), 39 deletions(-) diff --git a/src/pages/build/calling-a-zome-function.md b/src/pages/build/calling-a-zome-function.md index 41ec8a78c..f281a94d2 100644 --- a/src/pages/build/calling-a-zome-function.md +++ b/src/pages/build/calling-a-zome-function.md @@ -13,7 +13,7 @@ To call a zome function, use [`AppWebsocket.prototype.callZome`](https://github. This example gets all the movies by a given director, then logs their titles to the console. You'll need the [`getHolochainClient` example](/build/connecting-a-front-end/#connect-to-a-happ-with-the-javascript-client) from the Connecting to a Front End page. ```typescript -import { EntryHash } from '@holochain/client'; +import { decodeHashFromBase64, EntryHash } from '@holochain/client'; class Movie { title: string, @@ -29,12 +29,16 @@ async function logMoviesBySergioLeone() { const movies: Array = await client.callZome({ role_name: "movies", zome_name: "movies", - fn_name: "get_movies_by_director", + fn_name: "get_movies_for_director", // This should match the input payload struct for the zome function. - payload: { - director_hash: [ /* bytes of Sergio Leone director entry's hash */] - } + // The client serializes and deserializes hashes as 39-byte + // `Uint8Array`s. + // If your client ever needs to work with hashes as strings, the + // helper functions `decodeHashFromBase64` and `encodeHashToBase64` + // will do the conversions for you. + payload: decodeHashFromBase64(" -- base64-encoded Sergio Leone hash -- ") }); + console.log("Got movies"); for (let movie in movies) { console.log(movie.title); } @@ -50,27 +54,19 @@ Coordinator zomes can't call each other's functions directly; instead they do it This example calls a function `get_movies_by_director` in another zome called `movies` _in the same cell_: ```rust -use hdk::prelude::*; -// It's useful to put zome function input and output types in a separate crate -// from the zome itself, so they can be imported by zomes that call them. -use movies_types::*; - fn get_movies_by_sergio_leone() -> ExternResult> { - let input: GetMovieByDirectorInput = { - director_hash: [/* bytes of Sergio Leone director entry's hash */] - }; let response = call( // This indicates that `bar` is in the same cell. CallTargetCell::Local, "movies", - "get_movies_by_director", + "get_movies_for_director".into(), // This is a capability secret -- we'll explain why it's not needed. None, - input; + EntryHash::from_raw_36(vec![/* bytes of Sergio Leone director entry's hash */]), )?; match response { ZomeCallResponse::Ok(output) => output - .try_into() + .decode() .map_err(|e| wasm_error!(e.to_string()))?, _ => Err(wasm_error!("Something bad happened")), } @@ -84,21 +80,18 @@ use hdk::prelude::*; use movies_types::*; fn get_movies_by_sergio_leone_from_movies_cell() -> ExternResult> { - let input: GetMovieByDirectorInput = { - director_hash: [/* bytes of Sergio Leone director entry's hash */] - }; let response = call( // Address the cell by its role name. // You can also address it by its cell ID. - CallTargetCell::OtherRole("movies"), + CallTargetCell::OtherRole("movies".into()), "movies", - "get_movies_by_director", + "get_movies_for_director".into(), None, - input; + EntryHash::from_raw_36(vec![/* bytes of Sergio Leone director entry's hash */]), )?; match response { ZomeCallResponse::Ok(output) => output - .try_into() + .decode() .map_err(|e| wasm_error!(e.to_string()))?, _ => Err(wasm_error!("Something bad happened")), } @@ -119,33 +112,22 @@ This example calls a function _in the same coordinator zome_ (or at least one wi ```rust use hdk::prelude::*; +use movies_types::*; fn get_movies_by_sergio_leone_remote() -> ExternResult> { - let input: GetMovieByDirectorInput = { - director_hash: [/* bytes of Sergio Leone director entry's hash */] - }; - let bob_public_key: AgentPubKey = vec![/* bytes of remote agent's key */]; + let bob_public_key = AgentPubKey::from_raw_36(vec![/* bytes of remote agent's key */]); let response = call_remote( bob_public_key, // Get this zome's name from the host. zome_info()?.name, - "foo", + "foo".into(), // No capability secret needed for unrestricted functions. None, - foo_input, - ); - let response = call( - // Address the cell by its role name. - // You can also address it by its cell ID. - CallTargetCell::OtherRole("movies"), - "movies", - "get_movies_by_director", - None, - input; + EntryHash::from_raw_36(vec![/* bytes of Sergio Leone director entry's hash */]), )?; match response { ZomeCallResponse::Ok(output) => output - .try_into() + .decode() .map_err(|e| wasm_error!(e.to_string()))?, ZomeCallResponse::Unauthorized(_, _, _, _, _) => Err(wasm_error!("I wasn't allowed to call this function on Bob's device")), _ => Err(wasm_error!("Something bad happened")), From 911765b5e939cba48f29904dda4897e75a39c738 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 11 Feb 2025 09:44:41 -0800 Subject: [PATCH 15/29] rename to Connecting the parts --- src/pages/_data/navigation/mainNav.json5 | 4 ++-- ...cting-everything-together.md => connecting-the-parts.md} | 6 +++--- src/pages/build/index.md | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) rename src/pages/build/{connecting-everything-together.md => connecting-the-parts.md} (94%) diff --git a/src/pages/_data/navigation/mainNav.json5 b/src/pages/_data/navigation/mainNav.json5 index 392337e75..7ba4d8f15 100644 --- a/src/pages/_data/navigation/mainNav.json5 +++ b/src/pages/_data/navigation/mainNav.json5 @@ -37,8 +37,8 @@ { title: "Entries", url: "/build/entries/" }, { title: "Links, Paths, and Anchors", url: "/build/links-paths-and-anchors/" }, ]}, - { title: "Connecting Everything Together", url: "/build/connecting-everything-together/", children: [ - { title: "Connecting a Front End", url: "/build/connecting-a-front-end/" }, + { title: "Connecting the Parts", url: "/build/connecting-the-parts/", children: [ + { title: "Front End", url: "/build/connecting-a-front-end/" }, ]}, ] }, diff --git a/src/pages/build/connecting-everything-together.md b/src/pages/build/connecting-the-parts.md similarity index 94% rename from src/pages/build/connecting-everything-together.md rename to src/pages/build/connecting-the-parts.md index 355f13de9..cf0c8038a 100644 --- a/src/pages/build/connecting-everything-together.md +++ b/src/pages/build/connecting-the-parts.md @@ -1,12 +1,12 @@ --- -title: "Connecting Everything Together" +title: "Connecting the Parts" --- ::: topic-list ### In this section {data-no-toc} -* Connecting everything together (this page) - * [Connecting a front end](/build/connecting-a-front-end/) --- establishing a WebSocket connection from JavaScript +* Connecting the parts (this page) + * [Front end](/build/connecting-a-front-end/) --- establishing a WebSocket connection from JavaScript * Calling a zome function (coming soon) --- examples for front ends, cell-to-cell, and agent-to-agent * Capabilities (coming soon) --- how to manage access to a cell's zome functions * Working with signals (coming soon) --- receiving notifications from cells diff --git a/src/pages/build/index.md b/src/pages/build/index.md index a42835609..4ff3687cb 100644 --- a/src/pages/build/index.md +++ b/src/pages/build/index.md @@ -45,8 +45,8 @@ Now that you've got some basic concepts and the terms we use for them, it's time ## Connecting everything together ::: topic-list -* [Overview](/build/connecting-everything-together/) --- zome calls, capabilities, and signals -* [Connecting a front end](/build/connecting-a-front-end/) --- establishing a WebSocket connection from JavaScript +* [Overview](/build/connecting-the-parts/) --- zome calls, capabilities, and signals +* [Front end](/build/connecting-a-front-end/) --- establishing a WebSocket connection from JavaScript * Calling a zome function (coming soon) --- examples for front ends, cell-to-cell, and agent-to-agent * Capabilities (coming soon) --- how to manage access to a cell's zome functions * Working with signals (coming soon) --- receiving notifications from cells \ No newline at end of file From f88bf559d6fb9c4b91fd68c18837db0a848b8f70 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 11 Feb 2025 10:00:01 -0800 Subject: [PATCH 16/29] actually talk about hc-launch --- src/pages/build/connecting-a-front-end.md | 12 ++++++++++-- src/pages/build/happs.md | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/pages/build/connecting-a-front-end.md b/src/pages/build/connecting-a-front-end.md index ce2c714c6..3b91700a0 100644 --- a/src/pages/build/connecting-a-front-end.md +++ b/src/pages/build/connecting-a-front-end.md @@ -12,11 +12,19 @@ The most important thing to remember about a hApp is that it runs in the Holocha This interface is **only exposed to processes on the local device**, not to external network adapters. This helps prevent unauthorized agents from accessing the hApp. It also means the front end must be distributed with the hApp and a Holochain runtime. -Some Holochain runtimes bundle the conductor and a front end host that serves your HTML/JavaScript-based runtimes. Take a look at the [Packaging and Distribution](/get-started/4-packaging-and-distribution/) page from the Get Started guide for the choices. For this build guide, we'll be using `hc-launch`, a runtime used for testing your hApp, which comes with the Holonix dev environment. +Some Holochain runtimes bundle the conductor and a front end host that serves your HTML/JavaScript-based runtimes. Take a look at the [Packaging and Distribution](/get-started/4-packaging-and-distribution/) page from the Get Started guide for the choices. + +The Holonix dev environment comes with a runtime called `hc-launch`, which starts Holochain, installs your hApp, and displays a UI for you. The scaffolding tool generates an NPM script that compiles and bundles your back end into a [`.happ` file](/build/happs/#package-a-happ-for-distribution) and starts two instances of the hApp with `hc-launch`. In the root of your project folder, enter: + +```bash +npm run start +``` + +(If you're using a different package manager, change this command to suit.) ## Front-end libraries -Holochain provides client libraries for [JavaScript](https://github.com/holochain/holochain-client-js) and [Rust](https://github.com/holochain/holochain-client-rust). The scaffolding tool generates JavaScript-based UIs that are meant to be served as a single-page app, so we'll focus on JavaScript for this documentation --- or more specifically TypeScript, because that's what the client library is written in. +Holochain provides front-end client libraries for [JavaScript](https://github.com/holochain/holochain-client-js) and [Rust](https://github.com/holochain/holochain-client-rust). The scaffolding tool generates JavaScript-based UIs that are meant to be served as a single-page app, so we'll focus on JavaScript for this documentation --- or more specifically TypeScript, because that's what the client library is written in. ## Connect to a hApp with the JavaScript client diff --git a/src/pages/build/happs.md b/src/pages/build/happs.md index 7cc5a5208..b86189329 100644 --- a/src/pages/build/happs.md +++ b/src/pages/build/happs.md @@ -124,7 +124,7 @@ happ_manifest: * `ui`: The location of the UI zip file. You can use `bundled`, `path`, or `url`, just like you can with DNAs. * `happ-manifest`: The location of the hApp back end. -## Package a hApp for distribution +## Package a hApp for distribution {#package-a-happ-for-distribution} The first step to distributing your hApp is to bundle it into a `.happ` file, then bundle that file and a GUI into a `.webhapp` file. After that, you can go on to packaging it as a standalone binary or distributing it for runtimes that support multiple hApps. From e7426886d90971eb5ea6f6959662a63f52136081 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 11 Feb 2025 10:00:30 -0800 Subject: [PATCH 17/29] tiny text edit --- src/pages/build/connecting-the-parts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/build/connecting-the-parts.md b/src/pages/build/connecting-the-parts.md index cf0c8038a..ecc1556a9 100644 --- a/src/pages/build/connecting-the-parts.md +++ b/src/pages/build/connecting-the-parts.md @@ -13,7 +13,7 @@ title: "Connecting the Parts" ::: ::: intro -Your hApp back end's public interface consists of all the [**zome functions**](/build/zome-functions/) of all the [**cells**](/concepts/2_application_architecture/#cell) instantiated from all the [**DNAs**](/build/dnas/) that fill the hApp's [**roles**](/build/application-structure/#happ). It's accessible to locally running processes and to network peers, and is secured by a form of **capability-based security**, adapted for peer-to-peer applications. +Your hApp back end's public interface consists of all the [**zome functions**](/build/zome-functions/) of all the [**cells**](/concepts/2_application_architecture/#cell) instantiated from all the [**DNAs**](/build/dnas/) that fill the hApp's [**roles**](/build/application-structure/#happ). It is accessible to locally running processes and to network peers, and is secured by a form of **capability-based security**, adapted for peer-to-peer applications. The back end can also send out [**signals**](/concepts/9_signals/) that can be received either by UIs or remote peers. ::: From 66846c2c2dd91fa910cf87330b920f0da93a506b Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 11 Feb 2025 11:16:58 -0800 Subject: [PATCH 18/29] fix: broken fragment ID --- src/pages/build/happs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/build/happs.md b/src/pages/build/happs.md index b86189329..aec6a11ab 100644 --- a/src/pages/build/happs.md +++ b/src/pages/build/happs.md @@ -85,7 +85,7 @@ allow_deferred_memproofs: false Which creates a cell from the DNA immediately on hApp activation. * `dna`: The DNA that fills the role. * Location: The place to find the DNA bundle. The three options are: - * `bundled`: Expect the file to be part of this [bundle](#package-a-h-app-for-distribution). The value is a path relative to the manifest file. + * `bundled`: Expect the file to be part of this [bundle](#package-a-happ-for-distribution). The value is a path relative to the manifest file. * `path`: Get the file from the local filesystem. The value is a filesystem path. * `url`: Get the file from the web. The value is a URL, of course. * `modifiers`: Optional [integrity modifiers](/build/dnas/#integrity-modifiers) that change the DNA hash at install time. From e49e77bf8ccb2be506a400b29b9e3acd0206a0be Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 11 Feb 2025 11:30:59 -0800 Subject: [PATCH 19/29] fix: fragment ID --- src/pages/build/connecting-a-front-end.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/build/connecting-a-front-end.md b/src/pages/build/connecting-a-front-end.md index 3b91700a0..0befa8b29 100644 --- a/src/pages/build/connecting-a-front-end.md +++ b/src/pages/build/connecting-a-front-end.md @@ -26,7 +26,7 @@ npm run start Holochain provides front-end client libraries for [JavaScript](https://github.com/holochain/holochain-client-js) and [Rust](https://github.com/holochain/holochain-client-rust). The scaffolding tool generates JavaScript-based UIs that are meant to be served as a single-page app, so we'll focus on JavaScript for this documentation --- or more specifically TypeScript, because that's what the client library is written in. -## Connect to a hApp with the JavaScript client +## Connect to a hApp with the JavaScript client {#connect-to-a-happ-with-the-javascript-client} You connect to the application API with the client's [`AppWebsocket.connect`](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.connect.md) method, which returns a Promise<[AppWebsocket](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.md)>. From c5321aaff6af20486290a786bc6b0df7dbd32c5a Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 11 Feb 2025 11:47:01 -0800 Subject: [PATCH 20/29] edit: rename calling zome functions --- src/pages/_data/navigation/mainNav.json5 | 2 +- .../{calling-a-zome-function.md => calling-zome-functions.md} | 2 +- src/pages/build/index.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/pages/build/{calling-a-zome-function.md => calling-zome-functions.md} (99%) diff --git a/src/pages/_data/navigation/mainNav.json5 b/src/pages/_data/navigation/mainNav.json5 index 0628595c8..547978023 100644 --- a/src/pages/_data/navigation/mainNav.json5 +++ b/src/pages/_data/navigation/mainNav.json5 @@ -40,7 +40,7 @@ ]}, { title: "Connecting the Parts", url: "/build/connecting-the-parts/", children: [ { title: "Front End", url: "/build/connecting-a-front-end/" }, - { title: "Calling a Zome Function", url: "/build/calling-a-zome-function" }, + { title: "Calling Zome Functions", url: "/build/calling-zome-functions" }, ]}, ] }, diff --git a/src/pages/build/calling-a-zome-function.md b/src/pages/build/calling-zome-functions.md similarity index 99% rename from src/pages/build/calling-a-zome-function.md rename to src/pages/build/calling-zome-functions.md index f281a94d2..ea26b0f13 100644 --- a/src/pages/build/calling-a-zome-function.md +++ b/src/pages/build/calling-zome-functions.md @@ -1,5 +1,5 @@ --- -title: "Calling a zome function" +title: "Calling Zome Functions" --- ::: intro diff --git a/src/pages/build/index.md b/src/pages/build/index.md index f4d11b147..c2fac32e9 100644 --- a/src/pages/build/index.md +++ b/src/pages/build/index.md @@ -47,6 +47,6 @@ Now that you've got some basic concepts and the terms we use for them, it's time ::: topic-list * [Overview](/build/connecting-the-parts/) --- zome calls, capabilities, and signals * [Front end](/build/connecting-a-front-end/) --- establishing a WebSocket connection from JavaScript -* [Calling a zome function](/build/calling-a-zome-function/) --- examples for front ends, cell-to-cell, and agent-to-agent +* [Calling zome functions](/build/calling-zome-functions/) --- examples for front ends, cell-to-cell, and agent-to-agent * Capabilities (coming soon) --- how to manage access to a cell's zome functions * Working with signals (coming soon) --- receiving notifications from cells \ No newline at end of file From 6a522d34e9ba9338f0bec4508cfdad6a3c12f1e8 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 11 Feb 2025 13:01:49 -0800 Subject: [PATCH 21/29] fix: make Move class in TS compile --- src/pages/build/calling-zome-functions.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/build/calling-zome-functions.md b/src/pages/build/calling-zome-functions.md index ea26b0f13..3fd83b3f7 100644 --- a/src/pages/build/calling-zome-functions.md +++ b/src/pages/build/calling-zome-functions.md @@ -16,11 +16,11 @@ This example gets all the movies by a given director, then logs their titles to import { decodeHashFromBase64, EntryHash } from '@holochain/client'; class Movie { - title: string, - director_hash: EntryHash, - imdb_id: string | null, - release_date: Date, - box_office_revenue: Number, + title: string; + director_hash: EntryHash; + imdb_id: string | null; + release_date: Date; + box_office_revenue: Number; } async function logMoviesBySergioLeone() { From 45852af42bbbc1e3e314fb0a36ad84651fa1c01c Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 11 Feb 2025 13:06:53 -0800 Subject: [PATCH 22/29] fix: forgot to link to calling zome functions page from category page --- src/pages/build/connecting-the-parts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/build/connecting-the-parts.md b/src/pages/build/connecting-the-parts.md index ecc1556a9..11913f585 100644 --- a/src/pages/build/connecting-the-parts.md +++ b/src/pages/build/connecting-the-parts.md @@ -7,7 +7,7 @@ title: "Connecting the Parts" * Connecting the parts (this page) * [Front end](/build/connecting-a-front-end/) --- establishing a WebSocket connection from JavaScript - * Calling a zome function (coming soon) --- examples for front ends, cell-to-cell, and agent-to-agent + * [Calling zome functions](/build/calling-zome-functions) --- examples for front ends, cell-to-cell, and agent-to-agent * Capabilities (coming soon) --- how to manage access to a cell's zome functions * Working with signals (coming soon) --- receiving notifications from cells ::: From 795124274156b1a8fc07bd8f714ae7f1fcf9b48f Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 11 Feb 2025 13:27:09 -0800 Subject: [PATCH 23/29] fix: broken fragment ID --- src/pages/build/calling-zome-functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/build/calling-zome-functions.md b/src/pages/build/calling-zome-functions.md index d071066ca..f50cb3650 100644 --- a/src/pages/build/calling-zome-functions.md +++ b/src/pages/build/calling-zome-functions.md @@ -3,7 +3,7 @@ title: "Calling Zome Functions" --- ::: intro -You can call a zome function from [various local and remote processes](/build/connecting-everything-together/#what-processes-can-connect-to-a-happ) and secure them with capability tokens, letting you implement secure interop easily. +You can call a zome function from [various local and remote processes](/build/connecting-the-parts/#what-processes-can-connect-to-a-happ) and secure them with capability tokens, letting you implement secure interop easily. ::: ## Call a zome function from a front end From 52600b4d65f3028236649957ff272cf28c4eda21 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Thu, 13 Feb 2025 09:55:38 -0800 Subject: [PATCH 24/29] edit: accept recommendations from @jost-s --- src/pages/build/calling-zome-functions.md | 56 +++++++++++------------ src/pages/build/entries.md | 2 +- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/src/pages/build/calling-zome-functions.md b/src/pages/build/calling-zome-functions.md index f50cb3650..be15b5948 100644 --- a/src/pages/build/calling-zome-functions.md +++ b/src/pages/build/calling-zome-functions.md @@ -10,12 +10,12 @@ You can call a zome function from [various local and remote processes](/build/co To call a zome function, use [`AppWebsocket.prototype.callZome`](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.callzome.md). It takes a [`CallZomeRequest`](https://github.com/holochain/holochain-client-js/blob/main/docs/client.callzomerequest.md) and an optional timeout. -This example gets all the movies by a given director, then logs their titles to the console. You'll need the [`getHolochainClient` example](/build/connecting-a-front-end/#connect-to-a-happ-with-the-javascript-client) from the Connecting to a Front End page. +This example gets all the movies by a given director, then logs their titles to the console. You'll need the [`getHolochainClient` example from the Connecting to a Front End page](/build/connecting-a-front-end/#connect-to-a-happ-with-the-javascript-client). ```typescript import { decodeHashFromBase64, EntryHash } from '@holochain/client'; -class Movie { +interface Movie { title: string; director_hash: EntryHash; imdb_id: string | null; @@ -23,25 +23,16 @@ class Movie { box_office_revenue: Number; } -async function logMoviesBySergioLeone() { +async function getMoviesForDirector(directorHash: EntryHash): Array { // Use the `getHolochainClient` function from /build/connecting-a-front-end/ const client = await getHolochainClient(); - const movies: Array = await client.callZome({ + return await client.callZome({ role_name: "movies", zome_name: "movies", fn_name: "get_movies_for_director", // This should match the input payload struct for the zome function. - // The client serializes and deserializes hashes as 39-byte - // `Uint8Array`s. - // If your client ever needs to work with hashes as strings, the - // helper functions `decodeHashFromBase64` and `encodeHashToBase64` - // will do the conversions for you. - payload: decodeHashFromBase64(" -- base64-encoded Sergio Leone hash -- ") + payload: directorHash, }); - console.log("Got movies"); - for (let movie in movies) { - console.log(movie.title); - } } ``` @@ -49,21 +40,22 @@ The client handles errors (both `ExternResult::Err(_)` errors from the zome func ## Call a zome function from another zome in the same hApp -Coordinator zomes can't call each other's functions directly; instead they do it via the [`hdk::prelude::call`](https://docs.rs/hdk/latest/hdk/p2p/fn.call.html) host function. +Coordinator zomes call each other's functions via the [`hdk::prelude::call`](https://docs.rs/hdk/latest/hdk/p2p/fn.call.html) host function. It works for any coordinator zome in a hApp, whether in the same cell or another cell. -This example calls a function `get_movies_by_director` in another zome called `movies` _in the same cell_: +This example calls a function `get_movies_for_director` in another zome called `movies` _in the same cell_: ```rust -fn get_movies_by_sergio_leone() -> ExternResult> { +fn get_movies_for_director_from_movies_zome(director_hash: EntryHash) -> ExternResult> { let response = call( // This indicates that `bar` is in the same cell. CallTargetCell::Local, + // Call a coordinator zome by its name defined in the DNA manifest. "movies", "get_movies_for_director".into(), // This is a capability secret -- we'll explain later why it's not // needed. None, - EntryHash::from_raw_36(vec![/* bytes of Sergio Leone director entry's hash */]), + director_hash, )?; match response { ZomeCallResponse::Ok(output) => output @@ -74,21 +66,22 @@ fn get_movies_by_sergio_leone() -> ExternResult> { } ``` -This example calls that same function _if it's in a different cell_ whose role name is `movies`: +This example calls that same function in a zome called `movies` _in a different cell_ whose role name is also `movies`: ```rust use hdk::prelude::*; use movies_types::*; -fn get_movies_by_sergio_leone_from_movies_cell() -> ExternResult> { +fn get_movies_for_director_from_movies_cell(director_hash: EntryHash) -> ExternResult> { let response = call( - // Address the cell by its role name. - // You can also address it by its cell ID. + // Address the cell by its role name from the hApp manifest. + // You can also address it by its cell ID using + // `CallTargetCell::OtherCell(CellId)`. CallTargetCell::OtherRole("movies".into()), "movies", "get_movies_for_director".into(), None, - EntryHash::from_raw_36(vec![/* bytes of Sergio Leone director entry's hash */]), + director_hash, )?; match response { ZomeCallResponse::Ok(output) => output @@ -109,33 +102,36 @@ If two agents have cells running the same DNA --- that is, they're part of the s Holochain allows agents to add and remove coordinator zomes from their cells. This permits upgrading and customization. But it also means that the zomes and functions that you _think_ are on the other end might not actually be there. !!! -This example calls a function _in the same coordinator zome_ (or at least one with the same name) in a remote agent's cell. It assumes that the remote agent has granted access to their `get_movies_by_director` function with an [**unrestricted grant**](/concepts/8_calls_capabilities/#unrestricted), which doesn't require a capability secret. +This example calls a function _in the same coordinator zome_ (or at least one with the same name) in a remote agent's cell. It assumes that the remote agent has granted access to their `get_movies_by_director_from_movies_cell` function with an [**unrestricted grant**](/concepts/8_calls_capabilities/#unrestricted), which doesn't require a capability secret. ```rust use hdk::prelude::*; use movies_types::*; -fn get_movies_by_sergio_leone_remote() -> ExternResult> { - let bob_public_key = AgentPubKey::from_raw_36(vec![/* bytes of remote agent's key */]); +fn get_movies_for_director_remote(director_hash: EntryHash, remote_agent: AgentPubKey) -> ExternResult> { let response = call_remote( - bob_public_key, + remote_agent, // Get this zome's name from the host. zome_info()?.name, - "foo".into(), + "get_movies_by_director_from_movies_cell".into(), // No capability secret needed for unrestricted functions. None, - EntryHash::from_raw_36(vec![/* bytes of Sergio Leone director entry's hash */]), + director_hash, )?; match response { ZomeCallResponse::Ok(output) => output .decode() .map_err(|e| wasm_error!(e.to_string()))?, - ZomeCallResponse::Unauthorized(_, _, _, _, _) => Err(wasm_error!("I wasn't allowed to call this function on Bob's device")), + ZomeCallResponse::Unauthorized(_, _, _, _, _) => Err(wasm_error!("I wasn't allowed to call this function on remote device")), _ => Err(wasm_error!("Something bad happened")), } } ``` +!!! info All agents have access to the same DHT data +Because every agent in a network has the same view of the network's shared DHT data (assuming all agents are synced with each other), `call_remote` isn't useful for calling functions that get data from the DHT. Instead, it should be used for requesting data that another agent can access but you can't, such as asking them for [private entries](/build/entries/#private-entry-type) from their source chain or for data from a DHT that they can access but you can't. +!!! + ## Reference * [`AppWebsocket.prototype.callZome`](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.callzome.md) diff --git a/src/pages/build/entries.md b/src/pages/build/entries.md index 86e5f5a53..5da38617c 100644 --- a/src/pages/build/entries.md +++ b/src/pages/build/entries.md @@ -64,7 +64,7 @@ This also gives you an enum that you can use later when you're storing app data. 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. +* 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. {#private-entry-type} * 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. ```rust From d5c3bd02ad50504eaf92f5d6d7cbb7eb68fa24fd Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Thu, 13 Feb 2025 09:55:54 -0800 Subject: [PATCH 25/29] feat: write about identifiers in the front end --- src/pages/build/identifiers.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/pages/build/identifiers.md b/src/pages/build/identifiers.md index 335b22333..17b1600b0 100644 --- a/src/pages/build/identifiers.md +++ b/src/pages/build/identifiers.md @@ -221,6 +221,19 @@ let movie_to_loan_action_hash = create_link( Read more about [entries](/build/entries/) and [links](/build/links-paths-and-anchors/). +### In the front end + +The [JavaScript client](https://github.com/holochain/holochain-client-js/) defines an identifier as a [`HoloHash`](https://github.com/holochain/holochain-client-js/blob/main/docs/client.holohash.md), with aliases for each identifier type that take their names from the Rust types. Underneath, however, they're all just aliases of [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array). + +When this becomes hard to work with, such as when you need to receive an identifier as user input, we recommend using the client's `decodeHashFromBase64` and `encodeHashToBase64` functions, which convert between `Uint8Array` and a [URL-encoded Base64 string](https://datatracker.ietf.org/doc/html/rfc4648#section-5). + +```typescript +import type { EntryHash } from "@holochain/client"; +import { decodeHashFromBase64 } from "@holochain/client"; + +const sergioLeoneEntryHash: EntryHash = decodeHashFromBase64("uhCEkINI4cBkNicMiyEvxrUXKy71OIGWXqXJagdCunS2SlmaipQ1j"); +``` + ## The unpredictability of action hashes There are a few important things to know about action hashes: @@ -235,8 +248,6 @@ Because of these three things, it's unsafe to depend on the value or even existe * The same is also true of action hashes in your function's return value. * Don't communicate the action hash with the front end, another cell, or another peer on the network via a remote function call or [signal](/concepts/9_signals/) _from within the same function that writes it_, in case the write fails. Instead, do your communicating in a follow-up step. The easiest way to do this is by [implementing a callback called `post_commit`](/build/callbacks-and-lifecycle-hooks/#define-a-post-commit-callback) which receives a vector of all the actions that the function wrote. - - ## Reference * [`holo_hash` types](https://docs.rs/holo_hash/latest/holo_hash/#types) From 649efd8bab855c8ad7a8afae0eb6ced05b1b45ad Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Thu, 13 Feb 2025 09:57:17 -0800 Subject: [PATCH 26/29] edit: tiny edit for code clarity --- src/pages/build/calling-zome-functions.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/build/calling-zome-functions.md b/src/pages/build/calling-zome-functions.md index be15b5948..212f1ee75 100644 --- a/src/pages/build/calling-zome-functions.md +++ b/src/pages/build/calling-zome-functions.md @@ -26,13 +26,14 @@ interface Movie { async function getMoviesForDirector(directorHash: EntryHash): Array { // Use the `getHolochainClient` function from /build/connecting-a-front-end/ const client = await getHolochainClient(); - return await client.callZome({ + let movies: Array = await client.callZome({ role_name: "movies", zome_name: "movies", fn_name: "get_movies_for_director", // This should match the input payload struct for the zome function. payload: directorHash, }); + return movies; } ``` From be634a64f134020c44c3c7e0c4db7ef4f88e916d Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Thu, 13 Feb 2025 10:06:14 -0800 Subject: [PATCH 27/29] attempt to clarify wording about the author grant --- src/pages/build/calling-zome-functions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/build/calling-zome-functions.md b/src/pages/build/calling-zome-functions.md index 212f1ee75..64c397d16 100644 --- a/src/pages/build/calling-zome-functions.md +++ b/src/pages/build/calling-zome-functions.md @@ -37,7 +37,7 @@ async function getMoviesForDirector(directorHash: EntryHash): Array { } ``` -The client handles errors (both `ExternResult::Err(_)` errors from the zome function and other errors from Holochain itself) and deserializes the MessagePack return value for you. It also takes care of capability security for you. +The client handles errors (both `ExternResult::Err(_)` errors from yor zome function and other errors from Holochain itself), turning them into promise rejections, and deserializes the MessagePack return value into JavaScript objects for you on success. It also takes care of the details of [capability security](/build/connecting-the-parts/#securing-zome-functions-against-unauthorized-access) for you, so you don't have to provide any credentials for the zome call. ## Call a zome function from another zome in the same hApp @@ -93,7 +93,7 @@ fn get_movies_for_director_from_movies_cell(director_hash: EntryHash) -> ExternR } ``` -These cases don't need to worry about capability security either, because they're covered by a special grant called the [**author grant**](/concepts/8_calls_capabilities/#author-grant). It permits calls made by any caller with the same public key as the callee cell's owner. +Just as with front ends hosted by a supporting Holochain runtime, calls made within one agent's hApp instance don't need to supplying the right capability credentials, because under the hood Holochain applies a special capability called the [**author grant**](/concepts/8_calls_capabilities/#author-grant). The author grant automatically gives a caller access to any function if the agent ID of the callee cell matches the agent ID of the calling cell. ## Call a zome function from another agent in the network From 975d5aa32fd75c51504ed9fbe7e03322454ecf34 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Thu, 13 Feb 2025 10:55:23 -0800 Subject: [PATCH 28/29] edit: spruce up the calling zome functions a bit --- src/pages/build/calling-zome-functions.md | 28 +++++++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/pages/build/calling-zome-functions.md b/src/pages/build/calling-zome-functions.md index 64c397d16..11c6f6c3f 100644 --- a/src/pages/build/calling-zome-functions.md +++ b/src/pages/build/calling-zome-functions.md @@ -23,7 +23,7 @@ interface Movie { box_office_revenue: Number; } -async function getMoviesForDirector(directorHash: EntryHash): Array { +async function getMoviesForDirector(directorHash: EntryHash): Promise> { // Use the `getHolochainClient` function from /build/connecting-a-front-end/ const client = await getHolochainClient(); let movies: Array = await client.callZome({ @@ -46,7 +46,16 @@ Coordinator zomes call each other's functions via the [`hdk::prelude::call`](htt This example calls a function `get_movies_for_director` in another zome called `movies` _in the same cell_: ```rust -fn get_movies_for_director_from_movies_zome(director_hash: EntryHash) -> ExternResult> { +use hdk::prelude::*; +// When you want to share a set of entry type structs among multiple +// coordinator zomes, such as with the following zome that wants to +// deserialize the other zome function's return types, consider moving the +// Rust type definitions into a separate crates to be used by both the +// integrity zome and the coordinator zomes. +use movies_types::*; + +#[hdk_extern] +pub fn get_movies_for_director_from_movies_zome(director_hash: EntryHash) -> ExternResult> { let response = call( // This indicates that `bar` is in the same cell. CallTargetCell::Local, @@ -73,7 +82,8 @@ This example calls that same function in a zome called `movies` _in a different use hdk::prelude::*; use movies_types::*; -fn get_movies_for_director_from_movies_cell(director_hash: EntryHash) -> ExternResult> { +#[hdk_extern] +pub fn get_movies_for_director_from_movies_cell(director_hash: EntryHash) -> ExternResult> { let response = call( // Address the cell by its role name from the hApp manifest. // You can also address it by its cell ID using @@ -109,7 +119,15 @@ This example calls a function _in the same coordinator zome_ (or at least one wi use hdk::prelude::*; use movies_types::*; -fn get_movies_for_director_remote(director_hash: EntryHash, remote_agent: AgentPubKey) -> ExternResult> { +#[derive(Serialize, Deserialize, Debug)] +pub struct GetMoviesForDirectorRemoteInput { + pub director_hash: EntryHash, + pub remote_agent: AgentPubKey, +} + +#[hdk_extern] +pub fn get_movies_for_director_remote(input: GetMoviesForDirectorRemoteInput) -> ExternResult> { + let GetMoviesForDirectorRemoteInput { director_hash, remote_agent} = input; let response = call_remote( remote_agent, // Get this zome's name from the host. @@ -130,7 +148,7 @@ fn get_movies_for_director_remote(director_hash: EntryHash, remote_agent: AgentP ``` !!! info All agents have access to the same DHT data -Because every agent in a network has the same view of the network's shared DHT data (assuming all agents are synced with each other), `call_remote` isn't useful for calling functions that get data from the DHT. Instead, it should be used for requesting data that another agent can access but you can't, such as asking them for [private entries](/build/entries/#private-entry-type) from their source chain or for data from a DHT that they can access but you can't. +Because every agent in a network has the same view of the network's shared DHT data (assuming all agents are synced with each other), `call_remote` isn't useful for calling functions that get data from the DHT. Instead, it should be used for requesting data that another agent can access but you can't, such as asking them for [private entries](/build/entries/#private-entry-type) from their source chain or for data from a DHT that they can access but you can't. In the example above, imagine that the caller doesn't have a `movies` cell running but the callee does. !!! ## Reference From 84fef01a56df52dd09a3fa138a83660ffb0f9c4b Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Thu, 13 Feb 2025 12:11:35 -0800 Subject: [PATCH 29/29] fix: add chunks of a hash to dictionary to make CI pass --- .cspell/custom-words.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.cspell/custom-words.txt b/.cspell/custom-words.txt index 593291932..968fde8a9 100644 --- a/.cspell/custom-words.txt +++ b/.cspell/custom-words.txt @@ -53,3 +53,8 @@ WebRTC webview webviews workdir +# Some annoying bits of a hash in a code sample +Evxr +OIGW +Jagd +Slmaip \ No newline at end of file