From 750e8e2fdbbf1a4a7730e8e7c141d8721b0f3d18 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 9 Jul 2025 16:50:56 +0200 Subject: [PATCH 1/3] docs: asset generation example Signed-off-by: David Dal Busco --- .../functions/rust/components/references.md | 2 + .../rust/generating-assets-with-hooks.mdx | 240 ++++++++++++++++++ sidebars.ts | 3 +- 3 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 docs/examples/functions/rust/generating-assets-with-hooks.mdx diff --git a/docs/examples/functions/rust/components/references.md b/docs/examples/functions/rust/components/references.md index e0c46fb0..1c5291ae 100644 --- a/docs/examples/functions/rust/components/references.md +++ b/docs/examples/functions/rust/components/references.md @@ -18,3 +18,5 @@ These crates are used to build and extend serverless functions in Rust with Juno - [junobuild-satellite](https://docs.rs/junobuild-satellite): Core features and runtime for building a Satellite in Rust, including hooks, assertions, and datastore integration. - [junobuild-macros](https://docs.rs/junobuild-macros): Procedural macros for declaratively attaching hooks and assertions. - [junobuild-utils](https://docs.rs/junobuild-utils): Utility helpers for working with documents, including data encoding, decoding, and assertion context handling. +- [junobuild-shared](https://docs.rs/junobuild-shared): Shared types and helpers for Juno projects. Used by all containers including the Console. +- [junobuild-storage](https://docs.rs/junobuild-storage): Storage helpers for working with assets and HTTP headers in Juno. diff --git a/docs/examples/functions/rust/generating-assets-with-hooks.mdx b/docs/examples/functions/rust/generating-assets-with-hooks.mdx new file mode 100644 index 00000000..045c9c4f --- /dev/null +++ b/docs/examples/functions/rust/generating-assets-with-hooks.mdx @@ -0,0 +1,240 @@ +--- +title: Generating Assets with Rust Serverless Functions +description: An example showing how to dynamically generate and store assets (like JSON) in Storage using Rust in Juno Satellites. +keywords: + [rust, assets, serverless, functions, juno, satellite, storage, json, example] +sidebar_label: Asset Generation +--- + +# Generating Assets with Rust Serverless Functions + +This example demonstrates how to use **Rust serverless functions** to dynamically generate and store assets in **Juno Storage** from a **Satellite**. In this example, the generated assets are JSON files. + +Each time a note is added through the frontend, the Satellite saves the note as an individual JSON file and updates a list of all notes as another JSON file. This pattern is useful for exposing structured, queryable data as static assets — consumable by your frontend or external services. + +You can browse the source code here: [github.com/junobuild/examples/tree/main/rust/json](https://github.com/junobuild/examples/tree/main/rust/json) + +--- + +## Folder Structure + +``` +rust/json/ +├── src/ +│ ├── satellite/ # Rust Satellite serverless function +│ │ ├── src/ +│ │ │ ├── lib.rs # Main Rust logic for Satellite +│ │ │ └── generators.rs# Helper logic for JSON generation/storage +│ │ ├── satellite.did # Candid interface definition +│ │ └── Cargo.toml # Rust package config +│ ├── declarations/ # TypeScript declarations for Satellite +│ ├── lib/ # Svelte frontend components, stores, types +│ ├── routes/ # SvelteKit route files +│ ├── app.html # Svelte app entry +│ └── app.css # Styles +├── juno.config.ts # Juno Satellite configuration +├── package.json # Frontend dependencies +└── ... +``` + +--- + +## Key Features + +- **Serverless JSON Generation**: Demonstrates how to generate and store JSON files in Storage from Rust serverless functions. +- **Automatic List Updates**: Each note addition updates both the individual note JSON and a list of all notes as JSON. +- **Integration with Juno Storage**: Uses Juno's Storage API to expose JSON assets on the web. +- **Minimal SvelteKit UI**: A simple SvelteKit frontend is included to test and demonstrate the logic. + +--- + +## Main Backend Components + +- **src/satellite/src/lib.rs**: The entry point for the Satellite serverless function. Triggers JSON generation and list update on document set. +- **src/satellite/src/generators.rs**: Helper logic for encoding notes and lists as JSON and storing them as assets. +- **src/satellite/Cargo.toml**: Rust package configuration for the Satellite function. + +--- + +## Example: Generating and Storing JSON + +Here’s the actual Rust logic from `lib.rs` and `generators.rs`: + +```rust +// src/satellite/src/lib.rs +mod generators; + +use crate::generators::{generate_list_of_notes, generate_note}; +use junobuild_macros::on_set_doc; +use junobuild_satellite::{include_satellite, OnSetDocContext}; + +/// Hook triggered whenever a document is set (e.g., added or updated). +/// This example: +/// - Stores the updated document as an individual JSON file in Storage +/// - Updates a list of all note filenames as a separate JSON file +#[on_set_doc] +async fn on_set_doc(context: OnSetDocContext) -> Result<(), String> { + ic_cdk::print("Let's go!"); + + // Save the current note as a JSON asset + generate_note(&context.data.key, &context.data.data.after)?; + + // Regenerate the list of notes as a JSON array + generate_list_of_notes()?; + + Ok(()) +} + +// Boilerplate macro to include the all Satellite runtime +include_satellite!(); +``` + +```rust +// src/satellite/src/generators.rs + +use junobuild_satellite::{list_assets_store, set_asset_handler, Doc}; +use junobuild_shared::types::core::Key; +use junobuild_shared::types::list::ListParams; +use junobuild_storage::http::types::HeaderField; +use junobuild_storage::types::store::AssetKey; +use junobuild_utils::{decode_doc_data, encode_doc_data_to_string}; +use serde::{Deserialize, Serialize}; + +/// Represents the expected shape of a note stored in the Datastore +#[derive(Serialize, Deserialize)] +struct Note { + text: String, + url: Option, +} + +/// Encodes a note document as JSON and stores it as a `.json` file in Storage +pub fn generate_note(key: &Key, doc: &Doc) -> Result<(), String> { + let note: Note = decode_doc_data(&doc.data)?; + let json = encode_doc_data_to_string(¬e)?; + let name = format!("{}.json", key); + insert_asset(&name, &json) +} + +const STORAGE_COLLECTION: &str = "json"; + +/// Lists all assets in the `json` collection and stores their filenames +/// in a `notes.json` file — a JSON array of all note filenames +pub fn generate_list_of_notes() -> Result<(), String> { + let params: ListParams = ListParams { + matcher: None, + paginate: None, + order: None, + owner: None, + }; + + let result = list_assets_store(ic_cdk::id(), STORAGE_COLLECTION, ¶ms)?; + + // Extract the full paths of all assets in the collection + let list_of_keys: Vec = result + .items + .iter() + .map(|(_, asset)| asset.key.full_path.clone()) + .collect(); + + let json = encode_doc_data_to_string(&list_of_keys)?; + let name = "notes.json".to_string(); + + insert_asset(&name, &json)?; + Ok(()) +} + +/// Stores a given string as an asset in the `json` collection +fn insert_asset(name: &String, json: &String) -> Result<(), String> { + ic_cdk::print(format!("Json: {} {}", name, json)); + + let full_path = format!("/{}/{}", STORAGE_COLLECTION, name); + let key: AssetKey = AssetKey { + name: name.clone(), + full_path: full_path.clone(), + token: None, + collection: STORAGE_COLLECTION.to_string(), + owner: ic_cdk::id(), + description: None, + }; + + // Set appropriate headers for serving JSON + let headers = vec![HeaderField( + "content-type".to_string(), + "application/json".to_string(), + )]; + + // Upload asset to Juno Storage + set_asset_handler(&key, &json.as_bytes().to_vec(), &headers)?; + + ic_cdk::print(format!( + "Asset saved in Storage: http://{}.localhost:5987{}", + ic_cdk::id(), + full_path + )); + + Ok(()) +} +``` + +**Explanation:** + +- When a note is added or updated, the `on_set_doc` hook is triggered. +- The note is encoded as JSON and stored as an asset in the `json` collection. +- A list of all note asset paths is also generated and stored as `notes.json`. +- These JSON assets are accessible via the Storage API and can be fetched by the frontend or other clients. + +--- + +## How to Run + +1. **Clone the repo**: + +```bash +git clone https://github.com/junobuild/examples +cd rust/hooks +``` + +import HowToStart from "../../components/how-to-start.mdx"; + + + +import CreateSatellite from "../../components/create-a-satellite.mdx"; + + + +5. **Create required collections**: + +- `demo` in Datastore: [http://localhost:5866/datastore](http://localhost:5866/datastore) +- `json` in Storage: [http://localhost:5866/storage](http://localhost:5866/storage) + +import HowToRun from "../components/how-to-run.md"; + + + +--- + +## Juno-Specific Configuration + +- **juno.config.ts**: Defines Satellite IDs for development/production, build source, and predeploy steps. See the [Configuration reference](/docs/reference/configuration.md) for details. +- **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. + +--- + +## Production Deployment + +import ProdDeploy from "../components/prod-deploy.md"; + + + +--- + +## Notes + +- This example focuses on the Rust serverless function. The frontend is intentionally minimal and included only for demonstration. +- Use this project as a starting point for generate dynamic assets using Juno and Rust. + +--- + +import References from "./components/references.md"; + + diff --git a/sidebars.ts b/sidebars.ts index 52b0254a..d10e44ab 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -121,7 +121,8 @@ const sidebars: SidebarsConfig = { }, items: [ "examples/functions/rust/assertions", - "examples/functions/rust/mutating-docs-with-hooks" + "examples/functions/rust/mutating-docs-with-hooks", + "examples/functions/rust/generating-assets-with-hooks" ] } ] From a4cb0be69d8dfd49cf9cf3f1abec1e762fd0ac34 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 9 Jul 2025 16:53:25 +0200 Subject: [PATCH 2/3] fix: build Signed-off-by: David Dal Busco --- docs/examples/functions/rust/generating-assets-with-hooks.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/examples/functions/rust/generating-assets-with-hooks.mdx b/docs/examples/functions/rust/generating-assets-with-hooks.mdx index 045c9c4f..1b50eb43 100644 --- a/docs/examples/functions/rust/generating-assets-with-hooks.mdx +++ b/docs/examples/functions/rust/generating-assets-with-hooks.mdx @@ -215,8 +215,8 @@ import HowToRun from "../components/how-to-run.md"; ## Juno-Specific Configuration -- **juno.config.ts**: Defines Satellite IDs for development/production, build source, and predeploy steps. See the [Configuration reference](/docs/reference/configuration.md) for details. -- **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. +- **juno.config.ts**: Defines Satellite IDs for development/production, build source, and predeploy steps. See the [Configuration reference](../../../reference/configuration.mdx) for details. +- **vite.config.ts**: Registers the `juno` plugin to inject environment variables automatically. See the [Vite Plugin reference](../../../reference/plugins.mdx#vite-plugin) for more information. --- From 3da0e7a0811398bdba8140188a7410705f60432d Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 14:55:01 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=93=84=20Update=20LLMs.txt=20snapshot?= =?UTF-8?q?=20for=20PR=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .llms-snapshots/llms-full.txt | 171 +++++++++++++++++++++++++++++++++- .llms-snapshots/llms.txt | 1 + 2 files changed, 169 insertions(+), 3 deletions(-) diff --git a/.llms-snapshots/llms-full.txt b/.llms-snapshots/llms-full.txt index 57d0b585..ac7f3303 100644 --- a/.llms-snapshots/llms-full.txt +++ b/.llms-snapshots/llms-full.txt @@ -1300,7 +1300,7 @@ juno functions publish --mode staging --src ./path/to/build.wasm.gz ``` * 📤 Uploads to the Satellite’s CDN release. -* 🔐 Requires access key with **write** role. +* 🔐 Requires access key with **editor** role. #### b) Upgrade from CDN @@ -3286,6 +3286,169 @@ These crates are used to build and extend serverless functions in Rust with Juno * [junobuild-satellite](https://docs.rs/junobuild-satellite): Core features and runtime for building a Satellite in Rust, including hooks, assertions, and datastore integration. * [junobuild-macros](https://docs.rs/junobuild-macros): Procedural macros for declaratively attaching hooks and assertions. * [junobuild-utils](https://docs.rs/junobuild-utils): Utility helpers for working with documents, including data encoding, decoding, and assertion context handling. +* [junobuild-shared](https://docs.rs/junobuild-shared): Shared types and helpers for Juno projects. Used by all containers including the Console. +* [junobuild-storage](https://docs.rs/junobuild-storage): Storage helpers for working with assets and HTTP headers in Juno. + +# Generating Assets with Rust Serverless Functions + +This example demonstrates how to use **Rust serverless functions** to dynamically generate and store assets in **Juno Storage** from a **Satellite**. In this example, the generated assets are JSON files. + +Each time a note is added through the frontend, the Satellite saves the note as an individual JSON file and updates a list of all notes as another JSON file. This pattern is useful for exposing structured, queryable data as static assets — consumable by your frontend or external services. + +You can browse the source code here: [github.com/junobuild/examples/tree/main/rust/json](https://github.com/junobuild/examples/tree/main/rust/json) + +--- + +## Folder Structure + +``` +rust/json/├── src/│ ├── satellite/ # Rust Satellite serverless function│ │ ├── src/│ │ │ ├── lib.rs # Main Rust logic for Satellite│ │ │ └── generators.rs# Helper logic for JSON generation/storage│ │ ├── satellite.did # Candid interface definition│ │ └── Cargo.toml # Rust package config│ ├── declarations/ # TypeScript declarations for Satellite│ ├── lib/ # Svelte frontend components, stores, types│ ├── routes/ # SvelteKit route files│ ├── app.html # Svelte app entry│ └── app.css # Styles├── juno.config.ts # Juno Satellite configuration├── package.json # Frontend dependencies└── ... +``` + +--- + +## Key Features + +* **Serverless JSON Generation**: Demonstrates how to generate and store JSON files in Storage from Rust serverless functions. +* **Automatic List Updates**: Each note addition updates both the individual note JSON and a list of all notes as JSON. +* **Integration with Juno Storage**: Uses Juno's Storage API to expose JSON assets on the web. +* **Minimal SvelteKit UI**: A simple SvelteKit frontend is included to test and demonstrate the logic. + +--- + +## Main Backend Components + +* **src/satellite/src/lib.rs**: The entry point for the Satellite serverless function. Triggers JSON generation and list update on document set. +* **src/satellite/src/generators.rs**: Helper logic for encoding notes and lists as JSON and storing them as assets. +* **src/satellite/Cargo.toml**: Rust package configuration for the Satellite function. + +--- + +## Example: Generating and Storing JSON + +Here’s the actual Rust logic from `lib.rs` and `generators.rs`: + +``` +// src/satellite/src/lib.rsmod generators;use crate::generators::{generate_list_of_notes, generate_note};use junobuild_macros::on_set_doc;use junobuild_satellite::{include_satellite, OnSetDocContext};/// Hook triggered whenever a document is set (e.g., added or updated)./// This example:/// - Stores the updated document as an individual JSON file in Storage/// - Updates a list of all note filenames as a separate JSON file#[on_set_doc]async fn on_set_doc(context: OnSetDocContext) -> Result<(), String> { ic_cdk::print("Let's go!"); // Save the current note as a JSON asset generate_note(&context.data.key, &context.data.data.after)?; // Regenerate the list of notes as a JSON array generate_list_of_notes()?; Ok(())}// Boilerplate macro to include the all Satellite runtimeinclude_satellite!(); +``` + +``` +// src/satellite/src/generators.rsuse junobuild_satellite::{list_assets_store, set_asset_handler, Doc};use junobuild_shared::types::core::Key;use junobuild_shared::types::list::ListParams;use junobuild_storage::http::types::HeaderField;use junobuild_storage::types::store::AssetKey;use junobuild_utils::{decode_doc_data, encode_doc_data_to_string};use serde::{Deserialize, Serialize};/// Represents the expected shape of a note stored in the Datastore#[derive(Serialize, Deserialize)]struct Note { text: String, url: Option,}/// Encodes a note document as JSON and stores it as a `.json` file in Storagepub fn generate_note(key: &Key, doc: &Doc) -> Result<(), String> { let note: Note = decode_doc_data(&doc.data)?; let json = encode_doc_data_to_string(¬e)?; let name = format!("{}.json", key); insert_asset(&name, &json)}const STORAGE_COLLECTION: &str = "json";/// Lists all assets in the `json` collection and stores their filenames/// in a `notes.json` file — a JSON array of all note filenamespub fn generate_list_of_notes() -> Result<(), String> { let params: ListParams = ListParams { matcher: None, paginate: None, order: None, owner: None, }; let result = list_assets_store(ic_cdk::id(), STORAGE_COLLECTION, ¶ms)?; // Extract the full paths of all assets in the collection let list_of_keys: Vec = result .items .iter() .map(|(_, asset)| asset.key.full_path.clone()) .collect(); let json = encode_doc_data_to_string(&list_of_keys)?; let name = "notes.json".to_string(); insert_asset(&name, &json)?; Ok(())}/// Stores a given string as an asset in the `json` collectionfn insert_asset(name: &String, json: &String) -> Result<(), String> { ic_cdk::print(format!("Json: {} {}", name, json)); let full_path = format!("/{}/{}", STORAGE_COLLECTION, name); let key: AssetKey = AssetKey { name: name.clone(), full_path: full_path.clone(), token: None, collection: STORAGE_COLLECTION.to_string(), owner: ic_cdk::id(), description: None, }; // Set appropriate headers for serving JSON let headers = vec![HeaderField( "content-type".to_string(), "application/json".to_string(), )]; // Upload asset to Juno Storage set_asset_handler(&key, &json.as_bytes().to_vec(), &headers)?; ic_cdk::print(format!( "Asset saved in Storage: http://{}.localhost:5987{}", ic_cdk::id(), full_path )); Ok(())} +``` + +**Explanation:** + +* When a note is added or updated, the `on_set_doc` hook is triggered. +* The note is encoded as JSON and stored as an asset in the `json` collection. +* A list of all note asset paths is also generated and stored as `notes.json`. +* These JSON assets are accessible via the Storage API and can be fetched by the frontend or other clients. + +--- + +## How to Run + +1. **Clone the repo**: + +``` +git clone https://github.com/junobuild/examplescd rust/hooks +``` + +2. **Install dependencies**: + +``` +npm install +``` + +3. **Start Juno local emulator**: + +**Important:** + +Requires the Juno CLI to be available `npm i -g @junobuild/cli` + +``` +juno dev start +``` + +4. **Create a Satellite** for local dev: + +* Visit [http://localhost:5866](http://localhost:5866) and follow the instructions. +* Update `juno.config.ts` with your Satellite ID. + +5. **Create required collections**: + +* `demo` in Datastore: [http://localhost:5866/datastore](http://localhost:5866/datastore) +* `json` in Storage: [http://localhost:5866/storage](http://localhost:5866/storage) + +6. **Start the frontend dev server** (in a separate terminal): + +``` +npm run dev +``` + +7. **Build the serverless functions** (in a separate terminal): + +``` +juno functions build +``` + +The emulator will automatically upgrade your Satellite and live reload the changes. + +--- + +## Juno-Specific Configuration + +* **juno.config.ts**: Defines Satellite IDs for development/production, build source, and predeploy steps. See the [Configuration reference](/docs/reference/configuration.md) for details. +* **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. + +--- + +## Production Deployment + +* Create a Satellite on the [Juno Console](https://console.juno.build) for mainnet. +* Update `juno.config.ts` with the production Satellite ID. +* Build and deploy the frontend: + +``` +npm run buildjuno deploy +``` + +* Build and upgrade the serverless functions: + +``` +juno functions buildjuno functions upgrade +``` + +--- + +## Notes + +* This example focuses on the Rust serverless function. The frontend is intentionally minimal and included only for demonstration. +* Use this project as a starting point for generate dynamic assets using Juno and Rust. + +--- + +## References + +* [Serverless Functions Guide](/docs/guides/rust.md) +* [Rust Functions Development](/docs/build/functions.md) +* [Rust SDK Reference](/docs/reference/functions/rust/sdk.md) +* [Rust Utils Reference](/docs/reference/functions/rust/utils.md) +* [Run Local Development](/docs/guides/local-development.md) +* [CLI Reference](/docs/reference/cli.md) +* [Configuration Reference](/docs/reference/configuration.md) +* [Datastore Collections](/docs/build/datastore/collections.md) + +--- + +## Crate Docs + +These crates are used to build and extend serverless functions in Rust with Juno: + +* [junobuild-satellite](https://docs.rs/junobuild-satellite): Core features and runtime for building a Satellite in Rust, including hooks, assertions, and datastore integration. +* [junobuild-macros](https://docs.rs/junobuild-macros): Procedural macros for declaratively attaching hooks and assertions. +* [junobuild-utils](https://docs.rs/junobuild-utils): Utility helpers for working with documents, including data encoding, decoding, and assertion context handling. +* [junobuild-shared](https://docs.rs/junobuild-shared): Shared types and helpers for Juno projects. Used by all containers including the Console. +* [junobuild-storage](https://docs.rs/junobuild-storage): Storage helpers for working with assets and HTTP headers in Juno. # Mutating Documents with Rust Hooks @@ -3458,6 +3621,8 @@ These crates are used to build and extend serverless functions in Rust with Juno * [junobuild-satellite](https://docs.rs/junobuild-satellite): Core features and runtime for building a Satellite in Rust, including hooks, assertions, and datastore integration. * [junobuild-macros](https://docs.rs/junobuild-macros): Procedural macros for declaratively attaching hooks and assertions. * [junobuild-utils](https://docs.rs/junobuild-utils): Utility helpers for working with documents, including data encoding, decoding, and assertion context handling. +* [junobuild-shared](https://docs.rs/junobuild-shared): Shared types and helpers for Juno projects. Used by all containers including the Console. +* [junobuild-storage](https://docs.rs/junobuild-storage): Storage helpers for working with assets and HTTP headers in Juno. # Using Juno with AI @@ -5381,9 +5546,9 @@ name: Publish Serverless Functionson: workflow_dispatch: push: branches: [m This action will build and publish your serverless function bundle. -If your access key has a **write** role, the changes will be automatically deployed to your Satellite's CDN. +If your access key is an **editor**, the changes will be automatically deployed to your Satellite's CDN. -If your key only has a **submit** role, the release will be submitted as a pending change for manual approval. To avoid errors in submit-only workflows, you can explicitly use the `--no-apply` flag to skip auto-application. +If your key is only a **submitter**, the release will be proposed as a pending change for manual approval. To avoid errors in submit-only workflows, you can explicitly use the `--no-apply` flag to skip auto-application. ``` - name: Publish uses: junobuild/juno-action@full with: args: functions publish --no-apply env: JUNO_TOKEN: ${{ secrets.JUNO_TOKEN }} diff --git a/.llms-snapshots/llms.txt b/.llms-snapshots/llms.txt index afa2f78f..e870e48c 100644 --- a/.llms-snapshots/llms.txt +++ b/.llms-snapshots/llms.txt @@ -59,6 +59,7 @@ Juno is your self-contained serverless platform for building full-stack web apps ## Examples - Functions - Rust - [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. +- [Generating Assets with Rust Serverless Functions](https://juno.build/docs/examples/functions/rust/generating-assets-with-hooks.md): An example showing how to dynamically generate and store assets (like JSON) in Storage using Rust in Juno Satellites. - [Mutating Documents with Rust Hooks](https://juno.build/docs/examples/functions/rust/mutating-docs-with-hooks.md): An example demonstrating how to modify and re-save documents in Juno Satellites using Rust hooks. ## Guides