TypeScript Assertion Example
This example demonstrates how to write a custom assertion in TypeScript 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.
The project includes a minimal frontend to help trigger and test the logic, but the primary focus is the backend assertion.
You can browse the source code here: github.com/junobuild/examples/tree/main/functions/typescript/assertions
Folder Structure
typescript/assertions/
├── src/
│ ├── satellite/ # TypeScript Satellite serverless function
│ │ └── index.ts # Main TypeScript logic for Satellite
│ ├── types/
│ │ └── note.ts # Note type and schema
│ └── components/ # Minimal frontend React components
├── juno.config.ts # Juno Satellite configuration
├── package.json # Frontend and serverless dependencies
└── ...
Key Features
- Custom Assertions in TypeScript: Demonstrates how to reject or validate data before it's saved, using TypeScript serverless functions.
- 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 assertion logic in action.
Main Backend Components
- src/satellite/index.ts: The core TypeScript logic for the Satellite serverless function. Implements the custom assertions (e.g., only allow certain valid inputs, etc.).
- src/satellite/Cargo.toml: TypeScript package configuration for the Satellite function.
Example: Custom Assertion in TypeScript
Here’s the actual TypeScript logic from index.ts
:
import { type AssertSetDoc, defineAssert } from "@junobuild/functions";
import { decodeDocData } from "@junobuild/functions/sdk";
import { type NoteData, NoteDataSchema } from "../types/note";
export const assertSetDoc = defineAssert<AssertSetDoc>({
collections: ["notes"],
assert: (context) => {
const note = decodeDocData<NoteData>(context.data.data.proposed.data);
NoteDataSchema.parse(note);
if (note.text.toLowerCase().includes("hello")) {
console.log("❌ Rejected note containing 'hello':", note.text);
throw new Error("The note should not contain the keyword 'hello'.");
}
}
});
Explanation:
- Defines a
NoteData
type andNoteDataSchema
using zod for runtime validation. - Uses
defineAssert
to create a custom assertion for thenotes
collection. - When a note is created or updated, the assertion checks if the note's text contains the word "hello" (case-insensitive).
- If it does, the note is rejected and an error is thrown; otherwise, the note is accepted.
- Prints a message to the log for both accepted and rejected notes.
How to Run
- Clone the repo:
git clone https://github.com/junobuild/examples
cd typescript/assertions
2. Install dependencies:
npm install
3. Start Juno local emulator:
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 and follow the instructions.
- Update
juno.config.ts
with your Satellite ID.
- Create required collections:
notes
in Datastore: http://localhost:5866/datastoreimages
in Storage: http://localhost:5866/storage
- Start the frontend dev server (in a separate terminal):
npm run dev
- 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 for details.
- vite.config.ts: Registers the
juno
plugin to inject environment variables automatically. See the Vite Plugin reference for more information.
Production Deployment
- Create a Satellite on the Juno Console for mainnet.
- Update
juno.config.ts
with the production Satellite ID. - Build and deploy the frontend:
npm run build
juno deploy
- Build and upgrade the serverless functions:
juno functions build
juno functions upgrade
Notes
- This example focuses on the TypeScript serverless function; the frontend is intentionally minimal and only included for demonstration purposes.
- Use this project as a starting point for writing custom assertions and backend logic in TypeScript with Juno.
Real-World Example
Want to see how assertions and serverless logic are used in a live project?
Check out cycles.watch, an open-source app built with Juno:
- GitHub: github.com/peterpeterparker/cycles.watch
- Example logic: src/satellite/index.ts
This app uses:
assertSetDoc
to validate requestsonSetDoc
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.