Skip to content

Commit eb78efd

Browse files
docs: example calls typescript (#474)
* docs: example calls typescript 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 3689859 commit eb78efd

4 files changed

Lines changed: 476 additions & 3 deletions

File tree

.llms-snapshots/llms-full.txt

Lines changed: 184 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2233,7 +2233,7 @@ Write serverless backend logic for your app using TypeScript or Rust. These exam
22332233

22342234
[## 🗃️ TypeScript
22352235

2236-
2 items](/docs/examples/functions/typescript.md)
2236+
3 items](/docs/examples/functions/typescript.md)
22372237

22382238
# Angular Example
22392239

@@ -3189,6 +3189,10 @@ An example demonstrating how to write custom assertions in TypeScript for Juno s
31893189

31903190
An example demonstrating how to modify and re-save documents in Juno Satellites using TypeScript hooks.](/docs/examples/functions/typescript/mutating-docs.md)
31913191

3192+
[## 📄️ Canister Calls
3193+
3194+
An example showing how to call external canisters (e.g., ICRC ledger) from a serverless function written in TypeScript using Juno Satellites.](/docs/examples/functions/typescript/canister-calls.md)
3195+
31923196
# Rust Assertion Example
31933197

31943198
This example demonstrates how to write a **custom assertion** in **Rust** for a Juno **serverless function**. It shows how to intercept and validate data operations—such as rejecting specific content—before it's written to the datastore.
@@ -3549,7 +3553,9 @@ These crates are used to build and extend serverless functions in Rust with Juno
35493553
* [junobuild-shared](https://docs.rs/junobuild-shared): Shared types and helpers for Juno projects. Used by all containers including the Console.
35503554
* [junobuild-storage](https://docs.rs/junobuild-storage): Storage helpers for working with assets and HTTP headers in Juno.
35513555

3552-
\* [icrc-ledger-types](https://docs.rs/icrc-ledger-types): Types for interacting with the ICRC ledger standard. \* [ic-cdk](https://docs.rs/ic-cdk): Internet Computer canister development kit for Rust.
3556+
* [icrc-ledger-types](https://docs.rs/icrc-ledger-types): Types for interacting with the ICRC ledger standard.
3557+
3558+
* [ic-cdk](https://docs.rs/ic-cdk): Internet Computer canister development kit for Rust.
35533559

35543560
# Generating Assets with Rust Serverless Functions
35553561

@@ -4051,6 +4057,182 @@ It’s a great reference for more advanced setups and orchestration.
40514057
* [Configuration Reference](/docs/reference/configuration.md)
40524058
* [Datastore Collections](/docs/build/datastore/collections.md)
40534059

4060+
# Making Canister Calls in TypeScript Serverless Functions
4061+
4062+
This example demonstrates how to use **TypeScript serverless functions** to perform canister calls (such as `transfer_from` on the ICP ledger) in response to Datastore events in your Juno **Satellite**.
4063+
4064+
When a document is added to the `request` collection, a serverless function is triggered to:
4065+
4066+
* Check if the user has enough ICP in their wallet
4067+
* Transfer ICP from the user's wallet to the Satellite using the ICRC ledger's `transfer_from` method
4068+
* Mark the request as `processed` if the transfer succeeds
4069+
4070+
This pattern is useful for building workflows that require on-chain asset transfers or other canister calls in response to user actions.
4071+
4072+
You can browse the source code here: [github.com/junobuild/examples/tree/main/functions/typescript/calls](https://github.com/junobuild/examples/tree/main/functions/typescript/calls)
4073+
4074+
---
4075+
4076+
## Folder Structure
4077+
4078+
```
4079+
typescript/calls/├── src/│ ├── satellite/ # TypeScript Satellite serverless function│ │ ├── index.ts # Main TypeScript logic for Satellite│ │ ├── services.ts # Helper logic for balance, transfer, status│ │ ├── ledger-icrc.ts # Ledger helper functions│ │ └── tsconfig.json # TypeScript config for Satellite│ ├── declarations/│ │ └── satellite/ # TypeScript declarations for Satellite│ ├── components/ # React frontend components│ ├── services/ # Frontend service logic│ ├── types/ # Frontend type definitions│ ├── main.tsx # Frontend entry│ └── ...├── juno.config.ts # Juno Satellite configuration├── package.json # Frontend dependencies└── ...
4080+
```
4081+
4082+
---
4083+
4084+
## Key Features
4085+
4086+
* **Serverless Canister Calls**: Demonstrates how to perform ICRC ledger calls (e.g., `transfer_from`) from TypeScript serverless functions.
4087+
* **Atomic Request Processing**: Ensures that request status is only updated if the transfer succeeds.
4088+
* **Wallet Balance Checks**: Fails early if the user does not have enough ICP.
4089+
* **Minimal React UI**: A simple React frontend is included to test and demonstrate the logic.
4090+
4091+
---
4092+
4093+
## Main Backend Components
4094+
4095+
* **src/satellite/index.ts**: The entry point for the Satellite serverless function. Triggers the canister call and updates request status on document set.
4096+
* **src/satellite/services.ts**: Helper logic for checking wallet balance, performing the transfer, and updating request status.
4097+
* **src/satellite/ledger-icrc.ts**: Helper functions for interacting with the ICRC ledger.
4098+
* **src/types/request.ts**: Data model for requests and status.
4099+
4100+
---
4101+
4102+
## Example: Canister Call on Document Set
4103+
4104+
Here’s the actual TypeScript logic from `index.ts` and `services.ts`:
4105+
4106+
```
4107+
// src/satellite/index.tsimport { Account } from "@dfinity/ledger-icrc/dist/candid/icrc_ledger";import { Principal } from "@dfinity/principal";import { type AssertSetDoc, defineAssert, defineHook, type OnSetDoc} from "@junobuild/functions";import { id } from "@junobuild/functions/ic-cdk";import { decodeDocData } from "@junobuild/functions/sdk";import { COLLECTION_REQUEST, ICP_LEDGER_ID } from "../constants/app.constants";import { RequestData, RequestDataSchema } from "../types/request";import { assertWalletBalance, setRequestProcessed, transferIcpFromWallet} from "./services";export const assertSetDoc = defineAssert<AssertSetDoc>({ collections: [COLLECTION_REQUEST], assert: (context) => { // We validate that the data submitted for create or update matches the expected schema. const person = decodeDocData<RequestData>(context.data.data.proposed.data); RequestDataSchema.parse(person); }});export const onSetDoc = defineHook<OnSetDoc>({ collections: [COLLECTION_REQUEST], run: async (context) => { // Init data const { data: { key, data: { after: { version } } } } = context; const data = decodeDocData<RequestData>(context.data.data.after.data); const { amount: requestAmount, fee } = data; const ledgerId = ICP_LEDGER_ID; const fromAccount: Account = { owner: Principal.fromUint8Array(context.caller), subaccount: [] }; // Check current account balance await assertWalletBalance({ ledgerId, fromAccount, amount: requestAmount, fee }); // Update request status to processed (atomic with transfer) setRequestProcessed({ key, version, data }); // Transfer from wallet to satellite const toAccount: Account = { owner: id(), subaccount: [] }; await transferIcpFromWallet({ ledgerId, fromAccount, toAccount, amount: requestAmount, fee }); }});
4108+
```
4109+
4110+
```
4111+
// src/satellite/services.tsexport const assertWalletBalance = async ({ ledgerId, fromAccount, amount, fee}: { ledgerId: Principal; fromAccount: Account; amount: bigint; fee: bigint | undefined;}) => { const balance = await icrcBalanceOf({ ledgerId, account: fromAccount }); const total = amount + (fee ?? IC_TRANSACTION_FEE_ICP); if (balance < total) { throw new Error( `Balance ${balance} is smaller than ${total} for account ${fromAccount.owner.toText()}.` ); }};export const transferIcpFromWallet = async (params: { ledgerId: Principal; fromAccount: Account; toAccount: Account; amount: bigint; fee: bigint | undefined;}): Promise<bigint> => { const result = await icrcTransferFrom(params); if ("Err" in result) { throw new Error( `Failed to transfer ICP from wallet: ${JSON.stringify(result)}` ); } return result.Ok;};export const setRequestProcessed = ({ key, data: currentData, version: originalVersion}: { key: string; data: RequestData; version: bigint | undefined;}) => { const updateData: RequestData = { ...currentData, status: "processed" }; const data = encodeDocData(updateData); const doc: SetDoc = { data, version: originalVersion }; setDocStore({ caller: id(), collection: COLLECTION_REQUEST, doc, key });};
4112+
```
4113+
4114+
**Explanation:**
4115+
4116+
* When a request is submitted, the `onSetDoc` hook is triggered for the `request` collection.
4117+
* The function checks the user's wallet balance, updates the request status, and performs the ICP transfer atomically.
4118+
* If any step fails, the entire operation is reverted.
4119+
* The frontend can monitor request status and balances via the exposed APIs.
4120+
4121+
---
4122+
4123+
## How to Run
4124+
4125+
1. **Clone the repo**:
4126+
4127+
```
4128+
git clone https://github.com/junobuild/examplescd typescript/calls
4129+
```
4130+
4131+
2. **Install dependencies**:
4132+
4133+
```
4134+
npm install
4135+
```
4136+
4137+
3. **Start Juno local emulator**:
4138+
4139+
**Important:**
4140+
4141+
Requires the Juno CLI to be available `npm i -g @junobuild/cli`
4142+
4143+
```
4144+
juno dev start
4145+
```
4146+
4147+
4. **Create a Satellite** for local dev:
4148+
4149+
* Visit [http://localhost:5866](http://localhost:5866) and follow the instructions.
4150+
* Update `juno.config.ts` with your Satellite ID.
4151+
4152+
5. **Create required collections**:
4153+
4154+
* `request` in Datastore: [http://localhost:5866/datastore](http://localhost:5866/datastore)
4155+
4156+
6. **Start the frontend dev server** (in a separate terminal):
4157+
4158+
```
4159+
npm run dev
4160+
```
4161+
4162+
7. **Build the serverless functions** (in a separate terminal):
4163+
4164+
```
4165+
juno functions build
4166+
```
4167+
4168+
The emulator will automatically upgrade your Satellite and live reload the changes.
4169+
4170+
---
4171+
4172+
## Juno-Specific Configuration
4173+
4174+
* **juno.config.ts**: Defines Satellite IDs for development/production, build source, and predeploy steps. See the [Configuration reference](/docs/reference/configuration.md) for details.
4175+
* **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.
4176+
4177+
---
4178+
4179+
## Production Deployment
4180+
4181+
* Create a Satellite on the [Juno Console](https://console.juno.build) for mainnet.
4182+
* Update `juno.config.ts` with the production Satellite ID.
4183+
* Build and deploy the frontend:
4184+
4185+
```
4186+
npm run buildjuno deploy
4187+
```
4188+
4189+
* Build and upgrade the serverless functions:
4190+
4191+
```
4192+
juno functions buildjuno functions upgrade
4193+
```
4194+
4195+
---
4196+
4197+
## Notes
4198+
4199+
* This example focuses on the TypeScript serverless function. The frontend is intentionally minimal and included only for demonstration.
4200+
* Use this project as a starting point for writing custom backend logic in TypeScript using Juno serverless functions and canister calls.
4201+
4202+
---
4203+
4204+
## Real-World Example
4205+
4206+
Want to see how assertions and serverless logic are used in a live project?
4207+
4208+
Check out [cycles.watch](https://cycles.watch), an open-source app built with Juno:
4209+
4210+
* GitHub: [github.com/peterpeterparker/cycles.watch](https://github.com/peterpeterparker/cycles.watch)
4211+
* Example logic: [src/satellite/index.ts](https://github.com/peterpeterparker/cycles.watch/blob/main/src/satellite/index.ts)
4212+
4213+
This app uses:
4214+
4215+
* `assertSetDoc` to validate requests
4216+
* `onSetDoc` to implement a swap-like feature that performs various canister calls
4217+
* Service modules to keep logic organized
4218+
* A real-world pattern for chaining calls and document insertions with assertions
4219+
4220+
It’s a great reference for more advanced setups and orchestration.
4221+
4222+
---
4223+
4224+
## References
4225+
4226+
* [Serverless Functions Guide](/docs/guides/typescript.md)
4227+
* [Functions Development](/docs/build/functions.md)
4228+
* [TypeScript SDK Reference](/docs/reference/functions/typescript/sdk.md)
4229+
* [TypeScript ic-cdk Reference](/docs/reference/functions/typescript/ic-cdk.md)
4230+
* [TypeScript Utils Reference](/docs/reference/functions/typescript/utils.md)
4231+
* [Run Local Development](/docs/guides/local-development.md)
4232+
* [CLI Reference](/docs/reference/cli.md)
4233+
* [Configuration Reference](/docs/reference/configuration.md)
4234+
* [Datastore Collections](/docs/build/datastore/collections.md)
4235+
40544236
# Mutating Documents with TypeScript Hooks
40554237

40564238
This example demonstrates how to use **hooks in TypeScript** to modify documents automatically when they're created or updated in your Juno **Satellite**.

.llms-snapshots/llms.txt

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

7878
- [TypeScript Assertions Example](https://juno.build/docs/examples/functions/typescript/assertion.md): An example demonstrating how to write custom assertions in TypeScript for Juno serverless functions.
79+
- [Making Canister Calls in TypeScript Serverless Functions](https://juno.build/docs/examples/functions/typescript/canister-calls.md): An example showing how to call external canisters (e.g., ICRC ledger) from a serverless function written in TypeScript using Juno Satellites.
7980
- [Mutating Documents with TypeScript Hooks](https://juno.build/docs/examples/functions/typescript/mutating-docs.md): An example demonstrating how to modify and re-save documents in Juno Satellites using TypeScript hooks.
8081

8182
## Guides

0 commit comments

Comments
 (0)