Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build guide: Calling zome functions #528

Merged
merged 33 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
632a7c8
stub page for front end overview, add to nav
pdaoust Feb 5, 2025
731f181
Explanation and example for connecting from vanilla JS
pdaoust Feb 7, 2025
e0c0142
rename connection function
pdaoust Feb 7, 2025
9199a7d
fix unclosed formatting block
pdaoust Feb 7, 2025
a9dcddb
TOC for connecting everything together section
pdaoust Feb 7, 2025
9ee367b
connecting everything together overview page
pdaoust Feb 7, 2025
f56c9ae
add bibliograpy to connecting page
pdaoust Feb 7, 2025
1ab7b9e
mention signals in connecting page
pdaoust Feb 7, 2025
3e42cb6
mention signals in connecting page
pdaoust Feb 7, 2025
26a4e58
Merge branch 'feat/guide/connecting' into feat/guide/front-ends
pdaoust Feb 7, 2025
76b2d23
call a zome function page
pdaoust Feb 7, 2025
2f9c1ec
bibliography for zome calling page
pdaoust Feb 7, 2025
72cb320
fix zome code examples for call and call_remote
pdaoust Feb 7, 2025
c0e555b
turn conn getter into IIFE for compatibility
pdaoust Feb 7, 2025
c05a488
fix JS and Rust examples in zome calls page
pdaoust Feb 11, 2025
911765b
rename to Connecting the parts
pdaoust Feb 11, 2025
1b9030e
Merge remote-tracking branch 'origin/main' into feat/guide/connecting
pdaoust Feb 11, 2025
f88bf55
actually talk about hc-launch
pdaoust Feb 11, 2025
e742688
tiny text edit
pdaoust Feb 11, 2025
66846c2
fix: broken fragment ID
pdaoust Feb 11, 2025
59f4b20
Merge branch 'main' into feat/guide/calling-zome-functions
pdaoust Feb 11, 2025
e49e77b
fix: fragment ID
pdaoust Feb 11, 2025
c5321aa
edit: rename calling zome functions
pdaoust Feb 11, 2025
6a522d3
fix: make Move class in TS compile
pdaoust Feb 11, 2025
45852af
fix: forgot to link to calling zome functions page from category page
pdaoust Feb 11, 2025
8efbe83
Merge branch 'feat/guide/connecting' into feat/guide/calling-zome-fun…
pdaoust Feb 11, 2025
7951242
fix: broken fragment ID
pdaoust Feb 11, 2025
52600b4
edit: accept recommendations from @jost-s
pdaoust Feb 13, 2025
d5c3bd0
feat: write about identifiers in the front end
pdaoust Feb 13, 2025
649efd8
edit: tiny edit for code clarity
pdaoust Feb 13, 2025
be634a6
attempt to clarify wording about the author grant
pdaoust Feb 13, 2025
975d5aa
edit: spruce up the calling zome functions a bit
pdaoust Feb 13, 2025
84fef01
fix: add chunks of a hash to dictionary to make CI pass
pdaoust Feb 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .cspell/custom-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,8 @@ WebRTC
webview
webviews
workdir
# Some annoying bits of a hash in a code sample
Evxr
OIGW
Jagd
Slmaip
1 change: 1 addition & 0 deletions src/pages/_data/navigation/mainNav.json5
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
]},
]
},
Expand Down
164 changes: 164 additions & 0 deletions src/pages/build/calling-zome-functions.md
Original file line number Diff line number Diff line change
@@ -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<Array<Movie>> {
// Use the `getHolochainClient` function from /build/connecting-a-front-end/
const client = await getHolochainClient();
let movies: Array<Movie> = 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<Vec<Movie>> {
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<Vec<Movie>> {
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<Vec<Movie>> {
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)
2 changes: 1 addition & 1 deletion src/pages/build/connecting-a-front-end.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <code>Promise&lt;[AppWebsocket](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.md)&gt;</code>.

Expand Down
4 changes: 2 additions & 2 deletions src/pages/build/connecting-the-parts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
:::
Expand All @@ -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/)<!--TODO: change this to build guide link when signals is written--> 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.

Expand Down
2 changes: 1 addition & 1 deletion src/pages/build/entries.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <num>` argument.

```rust
Expand Down
15 changes: 13 additions & 2 deletions src/pages/build/identifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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.

<!-- TODO: write about the front end -->

## Reference

* [`holo_hash` types](https://docs.rs/holo_hash/latest/holo_hash/#types)
Expand Down
2 changes: 1 addition & 1 deletion src/pages/build/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion src/pages/concepts/8_calls_capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down