Skip to content

Commit 7793879

Browse files
docs: rust serverless functions hooks example (#464)
* docs: rust serverless functions hooks example Signed-off-by: David Dal Busco <david.dalbusco@outlook.com> * chore: real-world example Signed-off-by: David Dal Busco <david.dalbusco@outlook.com> * 📄 Update LLMs.txt snapshot for PR review --------- Signed-off-by: David Dal Busco <david.dalbusco@outlook.com> Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 58e02b9 commit 7793879

5 files changed

Lines changed: 487 additions & 4 deletions

File tree

.llms-snapshots/llms-full.txt

Lines changed: 201 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,14 @@ If you intend to use Juno solely for **[hosting](/docs/build/hosting.md)** purpo
145145

146146
---
147147

148+
## TL;DR
149+
150+
1. Call `initSatellite()` in your app code
151+
2. Create a `juno.config` file at the root to define your Satellite
152+
3. Connect code and config — preferably using the `@junobuild/nextjs-plugin` or `@junobuild/vite-plugin`
153+
154+
---
155+
148156
## Initialization
149157

150158
1. Install the Juno SDK:
@@ -187,6 +195,10 @@ npx @junobuild/cli init --minimal
187195

188196
This creates a `juno.config` file — in TypeScript, JavaScript, or JSON depending on your preferences — at the root of your project. It contains metadata such as the Satellite ID used during SDK initialization.
189197

198+
---
199+
200+
## Connecting Code and Config
201+
190202
If you're using **Next.js** or **Vite**, we recommend installing the official plugin. It automatically loads values from your config file and injects them into your build as environment variables.
191203

192204
This means you can call `initSatellite()` without passing any parameters, the SDK will read them automatically from `process.env` or `import.meta.env`.
@@ -3192,7 +3204,7 @@ juno dev start
31923204
npm run dev
31933205
```
31943206

3195-
6. **Build the serverless functions** (in a separate terminal):
3207+
7. **Build the serverless functions** (in a separate terminal):
31963208

31973209
```
31983210
juno functions build
@@ -3274,6 +3286,182 @@ These crates are used to build and extend serverless functions in Rust with Juno
32743286
* [junobuild-macros](https://docs.rs/junobuild-macros): Procedural macros for declaratively attaching hooks and assertions (e.g., `#[assert_set_doc]`, `#[on_delete_doc]`).
32753287
* [junobuild-utils](https://docs.rs/junobuild-utils): Utility helpers for working with documents, including data encoding, decoding, and assertion context handling.
32763288

3289+
# Rust Hooks Example
3290+
3291+
This example shows how to use **hooks in Rust** to customize how your Juno **Satellite** behaves. Hooks let you react to things like documents being created or deleted, or assets being uploaded — so you can run custom logic automatically on the backend.
3292+
3293+
The project includes a simple frontend to help you trigger and test the backend logic, but the real focus is on how the Rust serverless function works.
3294+
3295+
You can browse the source code here: [github.com/junobuild/examples/tree/main/rust/hooks](https://github.com/junobuild/examples/tree/main/rust/hooks)
3296+
3297+
---
3298+
3299+
## Folder Structure
3300+
3301+
```
3302+
rust/hooks/├── src/│ ├── satellite/ # Rust Satellite serverless function│ │ ├── src/│ │ │ └── lib.rs # Main Rust logic for Satellite│ │ ├── satellite.did # Candid interface definition│ │ └── Cargo.toml # Rust package config│ ├── declarations/ # TypeScript declarations for Satellite│ ├── admin.ts # Frontend admin logic│ ├── doc.ts # Frontend doc logic│ ├── main.ts # Frontend entry point│ ├── storage.ts # Frontend storage logic│ └── style.css # Frontend styles├── juno.config.ts # Juno Satellite configuration├── package.json # Frontend dependencies└── ...
3303+
```
3304+
3305+
---
3306+
3307+
## Key Features
3308+
3309+
* **Serverless Hooks in Rust**: Demonstrates how to react to data and asset operations using hooks in Rust serverless functions.
3310+
* **Multiple Hook Types**: Includes hooks for document set, set-many, delete, and asset upload operations.
3311+
* **Serverless Integration**: Runs as a Satellite function and integrates with Juno's datastore and authentication system.
3312+
* **Minimal UI for Testing**: A simple frontend is included to test and demonstrate the hook logic in action.
3313+
3314+
---
3315+
3316+
## Main Backend Components
3317+
3318+
* **src/satellite/src/lib.rs**: The core Rust logic for the Satellite serverless function. Implements hooks for various operations (set, set-many, delete, upload).
3319+
* **src/satellite/Cargo.toml**: Rust package configuration for the Satellite function.
3320+
3321+
---
3322+
3323+
## Example: Hooks in Rust
3324+
3325+
Here’s the actual Rust logic from `lib.rs`:
3326+
3327+
```
3328+
use ic_cdk::print;use junobuild_macros::{on_delete_doc, on_set_doc, on_set_many_docs, on_upload_asset};use junobuild_satellite::{ include_satellite, set_doc_store, OnDeleteDocContext, OnSetDocContext, OnSetManyDocsContext, OnUploadAssetContext, SetDoc,};use junobuild_utils::{decode_doc_data, encode_doc_data};use junobuild_utils::{DocDataBigInt, DocDataPrincipal};use serde::{Deserialize, Serialize};#[derive(Serialize, Deserialize)]struct Person { yolo: bool, hello: String, principal: DocDataPrincipal, value: DocDataBigInt,}// Hook that runs when a document is set in the "demo" collection#[on_set_doc(collections = ["demo"])]async fn on_set_doc(context: OnSetDocContext) -> Result<(), String> { // Decode the document into our Person struct let mut data: Person = decode_doc_data(&context.data.data.after.data)?; // Log some values for debugging print(format!("[on_set_doc] Caller: {}", context.caller.to_text())); print(format!("[on_set_doc] Collection: {}", context.data.collection)); print(format!("[on_set_doc] Data: {} {}", data.principal.value, data.value.value)); // Modify the document before storing it again data.hello = format!("{} checked", data.hello); data.yolo = false; // Encode and re-store the updated document let encode_data = encode_doc_data(&data)?; let doc: SetDoc = SetDoc { data: encode_data, description: context.data.data.after.description, version: context.data.data.after.version, }; set_doc_store( context.caller, context.data.collection, context.data.key, doc, )?; Ok(())}// Hook that runs when multiple documents are set in the "demo" collection#[on_set_many_docs(collections = ["demo"])]async fn on_set_many_docs(context: OnSetManyDocsContext) -> Result<(), String> { print(format!("Many docs called {}", context.data.len())); Ok(())}// Hook that runs when a document is deleted#[on_delete_doc]async fn on_delete_doc(_context: OnDeleteDocContext) -> Result<(), String> { print("Bye bye"); Ok(())}// Hook that runs when an asset is uploaded to Storage#[on_upload_asset]async fn on_upload_asset(context: OnUploadAssetContext) -> Result<(), String> { print(format!("Asset uploaded {}", context.data.key.full_path)); Ok(())}// A sample query function, accessible via `query` calls#[ic_cdk::query]fn say() { print("Hello");}include_satellite!();
3329+
```
3330+
3331+
**Explanation:**
3332+
3333+
* Defines a `Person` struct with fields for demo purposes.
3334+
* Uses the `#[on_set_doc]` macro to run logic whenever a document is set in the `demo` collection. Updates the document and saves it back.
3335+
* Uses the `#[on_set_many_docs]` macro to react to batch document sets in the `demo` collection.
3336+
* Uses the `#[on_delete_doc]` macro to react to document deletions.
3337+
* Uses the `#[on_upload_asset]` macro to react to asset uploads.
3338+
* Includes a simple query endpoint (`say`) for demonstration.
3339+
* `include_satellite!();` brings in the necessary boilerplate for the Juno Satellite runtime.
3340+
3341+
---
3342+
3343+
## How to Run
3344+
3345+
1. **Clone the repo**:
3346+
3347+
```
3348+
git clone https://github.com/junobuild/examplescd rust/hooks
3349+
```
3350+
3351+
2. **Install dependencies**:
3352+
3353+
```
3354+
npm install
3355+
```
3356+
3357+
3. **Start Juno local emulator**:
3358+
3359+
**Important:**
3360+
3361+
Requires the Juno CLI to be available `npm i -g @junobuild/cli`
3362+
3363+
```
3364+
juno dev start
3365+
```
3366+
3367+
4. **Create a Satellite** for local dev:
3368+
3369+
* Visit [http://localhost:5866](http://localhost:5866) and follow the instructions.
3370+
* Update `juno.config.ts` with your Satellite ID.
3371+
3372+
5. **Create required collections**:
3373+
3374+
* `demo` in Datastore: [http://localhost:5866/datastore](http://localhost:5866/datastore)
3375+
* `images` in Storage: [http://localhost:5866/storage](http://localhost:5866/storage)
3376+
3377+
6. **Start the frontend dev server** (in a separate terminal):
3378+
3379+
```
3380+
npm run dev
3381+
```
3382+
3383+
7. **Build the serverless functions** (in a separate terminal):
3384+
3385+
```
3386+
juno functions build
3387+
```
3388+
3389+
The emulator will automatically upgrade your Satellite and live reload the changes.
3390+
3391+
---
3392+
3393+
## Juno-Specific Configuration
3394+
3395+
* **juno.config.ts**: Defines Satellite IDs for development/production, build source, and predeploy steps. See the [Configuration reference](/docs/reference/configuration.md) for details.
3396+
* **vite.config.ts**: Registers the `juno` plugin to inject environment variables automatically. See the [Vite Plugin reference](/docs/reference/plugins.md#vite-plugin) for more information.
3397+
3398+
---
3399+
3400+
## Production Deployment
3401+
3402+
* Create a Satellite on the [Juno Console](https://console.juno.build) for mainnet.
3403+
* Update `juno.config.ts` with the production Satellite ID.
3404+
* Build and deploy the frontend:
3405+
3406+
```
3407+
npm run buildjuno deploy
3408+
```
3409+
3410+
* Build and upgrade the serverless functions:
3411+
3412+
```
3413+
juno functions buildjuno functions upgrade
3414+
```
3415+
3416+
---
3417+
3418+
## Notes
3419+
3420+
* This example focuses on the Rust serverless function hooks; the frontend is intentionally minimal and only included for demonstration purposes.
3421+
* Use this project as a starting point for writing custom hooks and backend logic in Rust with Juno.
3422+
3423+
---
3424+
3425+
## Real-World Example
3426+
3427+
Want to see how assertions and serverless logic are used in a live project?
3428+
3429+
Check out [proposals.network](https://proposals.network), an open-source app built with Juno:
3430+
3431+
* GitHub: [github.com/peterpeterparker/proposals.network](https://github.com/peterpeterparker/proposals.network)
3432+
* Example logic: [src/satellite/src/lib.rs](https://github.com/peterpeterparker/proposals.network/blob/main/src/satellite/src/lib.rs)
3433+
3434+
This app uses:
3435+
3436+
* `#[on_delete_doc]` and `#[assert_delete_doc]` to validate and clean up related documents and assets
3437+
* Shared helper modules like `assert`, `delete`, and `types` to keep logic organized
3438+
* A real-world pattern of chaining asset/document deletions with assertions
3439+
3440+
It’s a great reference for more advanced setups and multi-collection coordination.
3441+
3442+
---
3443+
3444+
## References
3445+
3446+
* [Serverless Functions Guide](/docs/guides/rust.md)
3447+
* [Rust Functions Development](/docs/build/functions.md)
3448+
* [Rust SDK Reference](/docs/reference/functions/rust/sdk.md)
3449+
* [Rust Utils Reference](/docs/reference/functions/rust/utils.md)
3450+
* [Run Local Development](/docs/guides/local-development.md)
3451+
* [CLI Reference](/docs/reference/cli.md)
3452+
* [Configuration Reference](/docs/reference/configuration.md)
3453+
* [Datastore Collections](/docs/build/datastore/collections.md)
3454+
3455+
---
3456+
3457+
## Crate Docs
3458+
3459+
These crates are used to build and extend serverless functions in Rust with Juno:
3460+
3461+
* [junobuild-satellite](https://docs.rs/junobuild-satellite): Core features and runtime for building a Satellite in Rust, including hooks, assertions, and datastore integration.
3462+
* [junobuild-macros](https://docs.rs/junobuild-macros): Procedural macros for declaratively attaching hooks and assertions (e.g., `#[on_set_doc]`, `#[on_delete_doc]`).
3463+
* [junobuild-utils](https://docs.rs/junobuild-utils): Utility helpers for working with documents, including data encoding, decoding, and assertion context handling.
3464+
32773465
# Using Juno with AI
32783466

32793467
If you're using AI to build with Juno, you can use our `llms.txt` files to help AI tools better understand the platform.
@@ -8503,6 +8691,18 @@ Make sure these two requirements are correctly met before restarting the command
85038691

85048692
---
85058693

8694+
### A Satellite ID is not configured. Juno cannot be initialized.
8695+
8696+
If you encounter the error:
8697+
8698+
> A Satellite ID is not configured. Juno cannot be initialized.
8699+
8700+
This means `initSatellite()` from the SDK is being called without a proper configuration. Most likely, the plugin responsible for loading your `juno.config` file values isn't set up correctly or is missing entirely.
8701+
8702+
To resolve this issue, make sure your SDK is correctly configured by following the steps in the documentation: [Setup SDK > Configuration](/docs/setup-the-sdk.md#configuration)
8703+
8704+
---
8705+
85068706
### Invalid character: "<"
85078707

85088708
When you scaffold an app with a template, the `juno.config` file includes placeholder values for the satellite IDs:

.llms-snapshots/llms.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ Juno is your self-contained serverless platform for building full-stack web apps
5959
## Examples - Functions - Rust
6060

6161
- [Rust Assertions Example](https://juno.build/docs/examples/functions/rust/assertions.md): An example demonstrating how to write custom assertions in Rust for Juno serverless functions.
62+
- [Rust Hooks Example](https://juno.build/docs/examples/functions/rust/hooks.md): An example demonstrating how to use serverless function hooks in Rust for Juno serverless functions.
6263

6364
## Guides
6465

docs/examples/functions/rust/assertions.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Rust Assertions Example
33
description: An example demonstrating how to write custom assertions in Rust for Juno serverless functions.
4-
keywords: [rust, assertion, serverless, juno, satellite, example]
4+
keywords: [rust, assertion, serverless, functions, juno, satellite, example]
55
sidebar_label: Assertions
66
---
77

@@ -140,7 +140,7 @@ juno dev start
140140
npm run dev
141141
```
142142

143-
6. **Build the serverless functions** (in a separate terminal):
143+
7. **Build the serverless functions** (in a separate terminal):
144144

145145
```bash
146146
juno functions build

0 commit comments

Comments
 (0)