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 diff --git a/src/pages/_data/navigation/mainNav.json5 b/src/pages/_data/navigation/mainNav.json5 index 567cf1228..547978023 100644 --- a/src/pages/_data/navigation/mainNav.json5 +++ b/src/pages/_data/navigation/mainNav.json5 @@ -40,6 +40,7 @@ ]}, { title: "Connecting the Parts", url: "/build/connecting-the-parts/", children: [ { title: "Front End", url: "/build/connecting-a-front-end/" }, + { title: "Calling Zome Functions", url: "/build/calling-zome-functions" }, ]}, ] }, diff --git a/src/pages/build/calling-zome-functions.md b/src/pages/build/calling-zome-functions.md new file mode 100644 index 000000000..11c6f6c3f --- /dev/null +++ b/src/pages/build/calling-zome-functions.md @@ -0,0 +1,164 @@ +--- +title: "Calling Zome Functions" +--- + +::: intro +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 + +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 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'; + +interface Movie { + title: string; + director_hash: EntryHash; + imdb_id: string | null; + release_date: Date; + box_office_revenue: Number; +} + +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({ + 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; +} +``` + +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 + +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_for_director` in another zome called `movies` _in the same cell_: + +```rust +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, + // 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, + director_hash, + )?; + match response { + ZomeCallResponse::Ok(output) => output + .decode() + .map_err(|e| wasm_error!(e.to_string()))?, + _ => Err(wasm_error!("Something bad happened")), + } +} +``` + +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::*; + +#[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 + // `CallTargetCell::OtherCell(CellId)`. + CallTargetCell::OtherRole("movies".into()), + "movies", + "get_movies_for_director".into(), + None, + director_hash, + )?; + match response { + ZomeCallResponse::Ok(output) => output + .decode() + .map_err(|e| wasm_error!(e.to_string()))?, + _ => Err(wasm_error!("Something bad happened")), + } +} +``` + +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 + +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 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_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::*; + +#[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. + zome_info()?.name, + "get_movies_by_director_from_movies_cell".into(), + // No capability secret needed for unrestricted functions. + None, + 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 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. In the example above, imagine that the caller doesn't have a `movies` cell running but the callee does. +!!! + +## 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 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)>. diff --git a/src/pages/build/connecting-the-parts.md b/src/pages/build/connecting-the-parts.md index ecc1556a9..aee9709b7 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 ::: @@ -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/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 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) diff --git a/src/pages/build/index.md b/src/pages/build/index.md index 05973ecf9..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 (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 \ No newline at end of file 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}