From 1bf47fae3afef5ab7f02f9ed288141e735ab2788 Mon Sep 17 00:00:00 2001 From: ponderingdemocritus Date: Sat, 9 Nov 2024 11:42:07 +1100 Subject: [PATCH] feat: docs --- packages/sdk/readme.md | 460 +---------------------------------------- 1 file changed, 1 insertion(+), 459 deletions(-) diff --git a/packages/sdk/readme.md b/packages/sdk/readme.md index 8032e4b2..38ccf12a 100644 --- a/packages/sdk/readme.md +++ b/packages/sdk/readme.md @@ -1,459 +1 @@ -# Dojo SDK: Build onchain faster - -The Dojo SDK provides a powerful, intuitive interface for interacting with onchain state. It streamlines data fetching and subscriptions, supporting both simple and complex queries. - -## Table of Contents - -- [Installation](#installation) -- [Usage](#usage) - - [Initializing the SDK](#initializing-the-sdk) - - [Understanding Queries](#understanding-queries) - - [Querying Entities](#querying-entities) - - [Subscribing to Entity Changes](#subscribing-to-entity-changes) - - [Sending Signed Messages](#sending-signed-messages) - - [Using with Zustand](#using-with-zustand) - - [Optimistic Client Rendering](#optimistic-client-rendering) -- [Advanced Usage](#advanced-usage) - - [Complex Queries](#complex-queries) -- [Troubleshooting](#troubleshooting) - -## Key Features - -- **Type Safety**: Leverage TypeScript for robust, error-resistant code. -- **Intuitive query syntax**: Write queries that feel natural, similar to popular ORMs. -- **Flexible subscriptions**: Easily subscribe to specific state changes in your Dojo world. -- **Signed messages**: Sign off-chain state and send to torii. -- **Automatic Zustand Support**: Drop in zustand state management -- **Optimistic Client Rendering**: Set state before a transaction has resolved to improve user experiences - -### Understand Entities and Models - -- Entities are uniquely identified by Keys defined in associated models -- Entities can have multiple models, representing complex game states -- When a subscription or query returns data - it returns the updated Entity and changed models. - -## Example: Subscribing to Specific Model States - -Here's a concise example demonstrating how to subscribe to the `item` model in the `world` namespace, specifically filtering for items with a durability of 2: - -```typescript -const subscription = await sdk.subscribeEntityQuery( - world: { - item: { - $: { - where: { durability: { $is: 2 } }, - }, - }, - }, - (response) => { - if (response.data) { - // return data - pipe into any state management! - console.log("Updated entities:", response.data); - } else if (response.error) { - // return error - console.error("Subscription error:", response.error); - } - } -); -``` - -# Usage - -## 🚀 Installation - -```bash -npm install @dojoengine/sdk -``` - -Generate TypeScript types directly from your world schema: - -To take advantage of this type safety (You will need [dojo](https://github.com/dojoengine/dojo) installed): - -1. Generate the TypeScript types for your world: - - ```bash - sozo build --typescript-v2 - ``` - -2. You will import these and pass into the sdk init function to give your app type. - -This approach ensures that your code remains in sync with your Dojo world definition, catching potential issues early in the development process. - -## Initializing the SDK - -```typescript -import { init, SchemaType } from "@dojoengine/sdk"; - -enum Direction { - None, - Up, - Down, - Left, - Right, -} - -interface Moves { - fieldOrder: string[]; - player: string; - last_direction: Direction; - can_move: boolean; -} -interface DirectionAvailable { - fieldOrder: string[]; - player: string; - direction: Direction[]; -} -interface Pos { - x: number; - y: number; -} -interface Position { - fieldOrder: string[]; - player: string; - vec: Pos; -} - -interface MockSchemaType { - dojo_starter: { - Moves: Moves; - DirectionAvailable: DirectionAvailable; - Position: Position; - }; -} - -// Generate with sozo or define Schema -const schema: Schema = { - // this should match namespace define in dojo - dojo_starter: { - // this has to match model names - Moves: { - fieldOrder: ["player", "remaining", "last_direction", "can_move"], - player: "", - remaining: 0, - // properties have to match too. - last_direction: Direction.None, - can_move: false, - }, - DirectionsAvailable: { - fieldOrder: ["player", "directions"], - player: "", - directions: [], - }, - Position: { - fieldOrder: ["player", "vec"], - player: "", - vec: { x: 0, y: 0 }, - }, - }, -}; - -// Initialize the SDK -const db = await init( - { - client: { - rpcUrl: "your-rpc-url", - toriiUrl: "your-torii-url", - relayUrl: "your-relay-url", - worldAddress: "your-world-address", - }, - domain: { - name: "Example", - version: "1.0", - chainId: "your-chain-id", - revision: "1", - }, - }, - schema -); - -// Voila! Now you have a typed interface -``` - -## Understanding Queries - -The SDK utilizes two primary types of queries to interact with the Dojo Engine: - -1. **`SubscriptionQueryType`**: Used for real-time subscriptions to entity and event updates. -2. **`QueryType`**: Used for fetching entities and event messages with more flexible filtering options. - -Both query types enable filtering based on `entityIds` and specific model properties. The key difference lies in the operators supported within the `where` clause: - -- **`SubscriptionQueryType`**: - - Supports only the `$is` operator for exact matches. -- **`QueryType`**: - - - Supports a variety of operators for more advanced filtering: - - | Operator | Description | - | -------- | ------------------------ | - | `$eq` | Equal to | - | `$neq` | Not equal to | - | `$gt` | Greater than | - | `$gte` | Greater than or equal to | - | `$lt` | Less than | - | `$lte` | Less than or equal to | - - - You combine queries with 'AND' with 'OR' from deep queries. See [Advanced Usage](#advanced-usage). - -## Querying Entities - -This example fetches `player` entities from the `world` namespace where `id` is "1" and `name` is "Alice", demonstrating multiple conditions in a query. - -Note: `$eq` is for exact matching. Other operators (`$gt`, `$lt`, etc.) are available for complex queries. - -```typescript -const entities = await sdk.getEntities( - { - // this is namespace to query models in - world: { - // this is model name - player: { - $: { where: { id: { $eq: "1" }, name: { $eq: "Alice" } } }, - }, - }, - }, - (response) => { - if (response.data) { - console.log("Fetched entities:", response.data); - } else if (response.error) { - console.error("Fetch error:", response.error); - } - } -); -``` - -## Subscribing To Entity Changes - -This example subscribes to `item` model updates in the `world` namespace, filtering for swords with durability 5. The callback triggers on matching item changes. - -Key points: - -- Namespace: `world`, Model: `item` -- Conditions: type "sword", durability 5 -- Uses `$is` for exact matching - -```typescript -const subscription = await sdk.subscribeEntityQuery( - { - // this is namespace to query models in - world: { - // this is model name - item: { - $: { - where: { - type: { $is: "sword" }, - durability: { $is: 5 }, - }, - }, - }, - }, - }, - (response) => { - if (response.data) { - console.log("Updated entities:", response.data); - } else if (response.error) { - console.error("Subscription error:", response.error); - } - } -); -``` - -## Sending Signed Messages - -**NOTE**: If you want messages to be actually sent and broadcasted to all of your torii client instance, -you'll have to properly set `relayUrl` in the `init` function. -`relayUrl` is a _multiaddr_ format which looks like something like this when deployed on slot: `/dns4/api.cartridge.gg/tcp/443/x-parity-wss/%2Fx%2Fonchain-dash%2Ftorii%2Fwss` - -```typescript -// onchain_dash-Message is a composition of the ${namespace}-${Model} type you want to sign. -// Here we take example of a chat because we don't want to load up those messages onchain -// But keep in mind this could be any models defined in your cairo code -const msg = sdk.generateTypedData("onchain_dash-Message", { - identity: account?.address, - content: toValidAscii(data.message), - timestamp: Date.now(), -}); - -try { - const signature = await account.signMessage(msg); - - try { - await db.client.publishMessage( - JSON.stringify(msg), - signature as string[] - ); - reset(); - } catch (error) { - console.error("failed to publish message:", error); - } -} catch (error) { - console.error("failed to sign message:", error); -} -``` - -## Using With Zustand - -This module takes the `Schema` and outputs a typed store you can use around your app. See example [here](../../examples/example-vite-react-sdk/). - -1. Import the module - -```typescript -import { createDojoStore } from "@dojoengine/sdk"; - -// import this outside of components -export const useDojoStore = createDojoStore(); - -... - -// Using in your app -const state = useDojoStore((state) => state); -const entities = useDojoStore((state) => state.entities); - -... - -// Adding to a callback -const subscription = await sdk.subscribeEntityQuery( - { - world: { - item: { - $: { - where: { - type: { $is: "sword" }, - durability: { $is: 5 }, - }, - }, - }, - }, - }, - (response) => { - if (response.error) { - console.error("Error setting up entity sync:", response.error); - } else if (response.data && response.data[0].entityId !== "0x0") { - // You just need to do this! - state.setEntities(response.data); - } - } -); -``` - -## Optimistic Client Rendering - -We use [immer](https://immerjs.github.io/immer/) for efficient optimistic rendering. This allows instant client-side entity state updates while awaiting blockchain confirmation. - -The process: - -1. Update entity state optimistically. -2. Wait for condition (e.g., a specific state change). -3. Resolve update, providing immediate user feedback. - -This ensures a responsive user experience while maintaining blockchain data integrity. - -See our [example project](../../examples/example-vite-react-sdk/src/useSystemCalls.ts) for a real-world implementation. - -Note: You will need to have a subscription running in order for the update to resolve. - -```typescript -export const useSystemCalls = () => { - const state = useDojoStore((state) => state); - - const { - setup: { client }, - account: { account }, - } = useDojo(); - - const generateEntityId = () => { - return getEntityIdFromKeys([BigInt(account?.address)]); - }; - - const spawn = async () => { - // Generate a unique entity ID - const entityId = generateEntityId(); - - // Generate a unique transaction ID - const transactionId = uuidv4(); - - // The value to update - const remainingMoves = 100; - - // Apply an optimistic update to the state - // this uses immer drafts to update the state - state.applyOptimisticUpdate( - transactionId, - (draft) => - (draft.entities[entityId].models.dojo_starter.Moves!.remaining = - remainingMoves) - ); - - try { - // Execute the spawn action - await client.actions.spawn({ account }); - - // Wait for the entity to be updated with the new state - await state.waitForEntityChange(entityId, (entity) => { - return ( - entity?.models?.dojo_starter?.Moves?.remaining === - remainingMoves - ); - }); - } catch (error) { - // Revert the optimistic update if an error occurs - state.revertOptimisticUpdate(transactionId); - console.error("Error executing spawn:", error); - throw error; - } finally { - // Confirm the transaction if successful - state.confirmTransaction(transactionId); - } - }; - - return { - spawn, - }; -}; -``` - -# Advanced Usage - -Create complex 'AND' with 'OR' statements to narrow in on what you want to fetch. - -## Complex Queries - -```typescript -const entities = await sdk.getEntities( - { - world: { - player: { - $: { - where: { - AND: [ - { score: { $gt: 100 } }, - { - OR: [ - { name: { $eq: "Alice" } }, - { name: { $eq: "Bob" } }, - ], - }, - ], - }, - }, - }, - item: { - $: { - where: { - AND: [{ durability: { $lt: 50 } }], - }, - }, - }, - }, - }, - (response) => { - if (response.data) { - console.log("Fetched entities:", response.data); - } else if (response.error) { - console.error("Fetch error:", response.error); - } - } -); -``` - -## Troubleshooting +See [Offical Docs](https://dojoengine.org/)