Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
171 changes: 170 additions & 1 deletion .llms-snapshots/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2233,7 +2233,7 @@ Write serverless backend logic for your app using TypeScript or Rust. These exam

[## 🗃️ TypeScript

1 item](/docs/examples/functions/typescript.md)
2 items](/docs/examples/functions/typescript.md)

# Angular Example

Expand Down Expand Up @@ -3185,6 +3185,10 @@ Examples of writing serverless functions in TypeScript for Juno. Includes patter

An example demonstrating how to write custom assertions in TypeScript for Juno serverless functions.](/docs/examples/functions/typescript/assertion.md)

[## 📄️ Mutation

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

# Rust Assertion Example

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.
Expand Down Expand Up @@ -4047,6 +4051,171 @@ It’s a great reference for more advanced setups and orchestration.
* [Configuration Reference](/docs/reference/configuration.md)
* [Datastore Collections](/docs/build/datastore/collections.md)

# Mutating Documents with TypeScript Hooks

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

Hooks let you react to events like document creation, deletion, or asset uploads — and run custom backend logic in response.

You can browse the source code here: [github.com/junobuild/examples/tree/main/functions/typescript/hooks](https://github.com/junobuild/examples/tree/main/functions/typescript/hooks)

---

## Folder Structure

```
typescript/hooks/├── src/│ ├── satellite/ # TypeScript Satellite serverless function│ │ ├── index.ts # Main TypeScript logic for Satellite│ │ └── tsconfig.json # TypeScript config for Satellite│ ├── declarations/│ │ └── satellite/ # 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│ └── types.ts # Shared types and schemas├── juno.config.ts # Juno Satellite configuration├── package.json # Frontend dependencies└── ...
```

---

## Key Features

* **Serverless Hooks in TypeScript**: Demonstrates how to react to data and asset operations using hooks in TypeScript serverless functions.
* **Multiple Hook Types**: Includes hooks for document set operations (extendable for set-many, delete, upload, etc.).
* **Serverless Integration**: Runs as a Satellite function and integrates with Juno's datastore and authentication system.
* **Minimal UI for Testing**: A simple frontend is included to test and demonstrate the hook logic in action.

---

## Main Backend Components

* **src/satellite/index.ts**: The core TypeScript logic for the Satellite serverless function. Implements hooks for various operations (set, assert, etc.).
* **src/types.ts**: Shared Zod schema and types for document validation and decoding.

---

## Example: Mutating Documents

Here’s the actual TypeScript logic from `index.ts`:

```
import { type AssertSetDoc, defineAssert, defineHook, type OnSetDoc} from "@junobuild/functions";import { PersonData, PersonDataSchema } from "../types";import { decodeDocData, encodeDocData, setDocStore} from "@junobuild/functions/sdk";import { Principal } from "@dfinity/principal";export const assertSetDoc = defineAssert<AssertSetDoc>({ collections: ["demo"], assert: (context) => { // We validate that the data submitted for create or update matches the expected schema. const person = decodeDocData<PersonData>(context.data.data.proposed.data); PersonDataSchema.parse(person); }});export const onSetDoc = defineHook<OnSetDoc>({ collections: ["demo"], run: async (context) => { const { caller, data: { key, collection, data: { after: currentDoc } } } = context; // We decode the new data saved in the Datastore because it holds those as blob. const person = decodeDocData<PersonData>(currentDoc.data); // Some console.log for demo purpose console.log( "[on_set_doc] Caller:", Principal.fromUint8Array(caller).toText() ); console.log("[on_set_doc] Collection:", collection); console.log("[on_set_doc] Data:", person.principal, person.value); // We update the document's data that was saved in the Datastore with the call from the frontend dapp. const { hello, ...rest } = person; const updatePerson = { ...rest, hello: `${hello} checked`, yolo: false }; // We encode the data back to blob. const updateData = encodeDocData(updatePerson); // We save the document for the same caller as the one who triggered the original on_set_doc, in the same collection with the same key as well. setDocStore({ caller: caller, collection, key, doc: { version: currentDoc.version, data: updateData } }); }});
```

**Explanation:**

* Defines a `PersonData` type and Zod schema for validation.
* Uses `defineAssert` to validate document data before creation or update.
* Uses `defineHook` to run logic whenever a document is set in the `demo` collection. Updates the document and saves it back.
* Uses the Juno SDK for encoding/decoding and storing documents.

---

## How to Run

1. **Clone the repo**:

```
git clone https://github.com/junobuild/examplescd typescript/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)
* `images` 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 TypeScript serverless function. The frontend is intentionally minimal and included only for demonstration.
* Use this project as a starting point for writing custom backend logic in TypeScript using Juno hooks.

---

## Real-World Example

Want to see how assertions and serverless logic are used in a live project?

Check out [cycles.watch](https://cycles.watch), an open-source app built with Juno:

* GitHub: [github.com/peterpeterparker/cycles.watch](https://github.com/peterpeterparker/cycles.watch)
* Example logic: [src/satellite/index.ts](https://github.com/peterpeterparker/cycles.watch/blob/main/src/satellite/index.ts)

This app uses:

* `assertSetDoc` to validate requests
* `onSetDoc` to implement a swap-like feature that performs various canister calls
* Service modules to keep logic organized
* A real-world pattern for chaining calls and document insertions with assertions

It’s a great reference for more advanced setups and orchestration.

---

## References

* [Serverless Functions Guide](/docs/guides/typescript.md)
* [Functions Development](/docs/build/functions.md)
* [TypeScript SDK Reference](/docs/reference/functions/typescript/sdk.md)
* [TypeScript ic-cdk Reference](/docs/reference/functions/typescript/ic-cdk.md)
* [TypeScript Utils Reference](/docs/reference/functions/typescript/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)

# Using Juno with AI

If you're using AI to build with Juno, you can use our `llms.txt` files to help AI tools better understand the platform.
Expand Down
1 change: 1 addition & 0 deletions .llms-snapshots/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Juno is your self-contained serverless platform for building full-stack web apps
## Examples - Functions - Typescript

- [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.
- [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.

## Guides

Expand Down
197 changes: 197 additions & 0 deletions docs/examples/functions/typescript/mutating-docs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
---
title: Mutating Documents with TypeScript Hooks
description: An example demonstrating how to modify and re-save documents in Juno Satellites using TypeScript hooks.
keywords: [typescript, hooks, serverless, functions, juno, satellite, example]
sidebar_label: Mutation
---

# Mutating Documents with TypeScript Hooks

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

Hooks let you react to events like document creation, deletion, or asset uploads — and run custom backend logic in response.

You can browse the source code here: [github.com/junobuild/examples/tree/main/functions/typescript/hooks](https://github.com/junobuild/examples/tree/main/functions/typescript/hooks)

---

## Folder Structure

```
typescript/hooks/
├── src/
│ ├── satellite/ # TypeScript Satellite serverless function
│ │ ├── index.ts # Main TypeScript logic for Satellite
│ │ └── tsconfig.json # TypeScript config for Satellite
│ ├── declarations/
│ │ └── satellite/ # 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
│ └── types.ts # Shared types and schemas
├── juno.config.ts # Juno Satellite configuration
├── package.json # Frontend dependencies
└── ...
```

---

## Key Features

- **Serverless Hooks in TypeScript**: Demonstrates how to react to data and asset operations using hooks in TypeScript serverless functions.
- **Multiple Hook Types**: Includes hooks for document set operations (extendable for set-many, delete, upload, etc.).
- **Serverless Integration**: Runs as a Satellite function and integrates with Juno's datastore and authentication system.
- **Minimal UI for Testing**: A simple frontend is included to test and demonstrate the hook logic in action.

---

## Main Backend Components

- **src/satellite/index.ts**: The core TypeScript logic for the Satellite serverless function. Implements hooks for various operations (set, assert, etc.).
- **src/types.ts**: Shared Zod schema and types for document validation and decoding.

---

## Example: Mutating Documents

Here’s the actual TypeScript logic from `index.ts`:

```ts
import {
type AssertSetDoc,
defineAssert,
defineHook,
type OnSetDoc
} from "@junobuild/functions";
import { PersonData, PersonDataSchema } from "../types";
import {
decodeDocData,
encodeDocData,
setDocStore
} from "@junobuild/functions/sdk";
import { Principal } from "@dfinity/principal";

export const assertSetDoc = defineAssert<AssertSetDoc>({
collections: ["demo"],
assert: (context) => {
// We validate that the data submitted for create or update matches the expected schema.
const person = decodeDocData<PersonData>(context.data.data.proposed.data);
PersonDataSchema.parse(person);
}
});

export const onSetDoc = defineHook<OnSetDoc>({
collections: ["demo"],
run: async (context) => {
const {
caller,
data: {
key,
collection,
data: { after: currentDoc }
}
} = context;

// We decode the new data saved in the Datastore because it holds those as blob.
const person = decodeDocData<PersonData>(currentDoc.data);

// Some console.log for demo purpose
console.log(
"[on_set_doc] Caller:",
Principal.fromUint8Array(caller).toText()
);
console.log("[on_set_doc] Collection:", collection);
console.log("[on_set_doc] Data:", person.principal, person.value);

// We update the document's data that was saved in the Datastore with the call from the frontend dapp.
const { hello, ...rest } = person;
const updatePerson = {
...rest,
hello: `${hello} checked`,
yolo: false
};

// We encode the data back to blob.
const updateData = encodeDocData(updatePerson);

// We save the document for the same caller as the one who triggered the original on_set_doc, in the same collection with the same key as well.
setDocStore({
caller: caller,
collection,
key,
doc: {
version: currentDoc.version,
data: updateData
}
});
}
});
```

**Explanation:**

- Defines a `PersonData` type and Zod schema for validation.
- Uses `defineAssert` to validate document data before creation or update.
- Uses `defineHook` to run logic whenever a document is set in the `demo` collection. Updates the document and saves it back.
- Uses the Juno SDK for encoding/decoding and storing documents.

---

## How to Run

1. **Clone the repo**:

```bash
git clone https://github.com/junobuild/examples
cd typescript/hooks
```

import HowToStart from "../../components/how-to-start.mdx";

<HowToStart index={2} />

import CreateSatellite from "../../components/create-a-satellite.mdx";

<CreateSatellite index={4} />

5. **Create required collections**:

- `demo` in Datastore: [http://localhost:5866/datastore](http://localhost:5866/datastore)
- `images` in Storage: [http://localhost:5866/storage](http://localhost:5866/storage)

import HowToRun from "../components/how-to-run.md";

<HowToRun />

---

import Config from "../components/config.md";

<Config />

---

import ProdDeploy from "../components/prod-deploy.md";

<ProdDeploy />

---

## Notes

- This example focuses on the TypeScript serverless function. The frontend is intentionally minimal and included only for demonstration.
- Use this project as a starting point for writing custom backend logic in TypeScript using Juno hooks.

---

import CyclesWatch from "./components/cycles-watch.md";

<CyclesWatch />

---

import References from "./components/references.md";

<References />
Loading