### Wispr Example: Player Inventory Server Setup (TypeScript) Source: https://github.com/sawcyd/wispr/blob/master/README.md Demonstrates server-side setup for a player inventory system using Wispr. Includes creating tokens, initializing nodes for players joining, and functions to update inventory via patching. ```typescript // Server import { Players } from "@rbxts/services"; import { WisprToken, createNode, patchNode, pathOf, opIncrement, opListPush } from "@rbxts/wispr"; interface Inventory { gold: number; items: string[]; equipment: Record; } // Create a token factory for per-player inventories function getPlayerInventoryToken(player: Player): WisprToken { return WisprToken.create(`player.${player.UserId}.inventory`); } // Create per-player inventory Players.PlayerAdded.Connect((player) => { const token = getPlayerInventoryToken(player); createNode( token, { kind: "player", player }, // Scope to this specific player { gold: 0, items: [], equipment: {} } ); }); // Update inventory for a specific player function addGold(player: Player, amount: number) { const token = getPlayerInventoryToken(player); patchNode(token, opIncrement(pathOf("gold"), amount)); } function addItem(player: Player, item: string) { const token = getPlayerInventoryToken(player); patchNode(token, opListPush(pathOf("items"), item)); } ``` -------------------------------- ### Wispr Example: Player Inventory Client Setup (TypeScript) Source: https://github.com/sawcyd/wispr/blob/master/README.md Illustrates client-side logic for interacting with a player inventory system using Wispr. It requests initial data, waits for the inventory node, and sets up listeners to update UI elements based on changes in gold and items. ```typescript // Client import { Players } from "@rbxts/services"; import { waitForNode, requestInitialData, pathOf, WisprToken } from "@rbxts/wispr"; await requestInitialData(); // Get the player's own inventory token const localPlayer = Players.LocalPlayer; if (!localPlayer) return; const inventoryToken = WisprToken.create(`player.${localPlayer.UserId}.inventory`); const inventory = await waitForNode(inventoryToken); // Display gold inventory.listenForChange(pathOf("gold"), (newGold) => { updateGoldUI(newGold as number); }); // Display items inventory.listenForChange(pathOf("items"), (newItems) => { updateItemsUI(newItems as string[]); }); ``` -------------------------------- ### Client Setup: Initialize, Access, and Listen to State with Wispr (TypeScript) Source: https://github.com/sawcyd/wispr/blob/master/README.md Illustrates the client-side setup for Wispr. It covers initializing the client, waiting for and retrieving state nodes, and setting up listeners for specific state changes or any state updates. It also shows how to listen for nodes matching a pattern. ```typescript import { waitForNode, requestInitialData, pathOf } from "@rbxts/wispr"; // Initialize (call once on client startup) await requestInitialData(); // Wait for and get the node const node = await waitForNode(PLAYER_STATS); const stats = node.getState(); // Listen for changes node.listenForChange(pathOf("gold"), (newVal, oldVal) => { print(`Gold changed from ${oldVal} to ${newVal}`); }); // Or listen for any change node.listenForAnyChange(() => { print("Stats updated!"); }); // Listen for nodes matching a pattern (similar to ReplicaService) import { onNodeOfClassCreated, requestInitialData } from "@rbxts/wispr"; await requestInitialData(); // Listen for any player data node (e.g., "player.data.123", "player.data.456") onNodeOfClassCreated("player.data.", (node) => { print(`Player data node created: ${node.token.id}`); const data = node.getState(); // Setup UI, listeners, etc. for this player's data node.listenForChange(pathOf("coins"), (newCoins) => { updateCoinsUI(newCoins as number); }); }); ``` -------------------------------- ### Player Inventory Example Source: https://github.com/sawcyd/wispr/blob/master/README.md Example demonstrating how to create and manage player inventories using Wispr on both server and client. ```APIDOC ## Example: Player Inventory ### Server-Side Implementation This example shows how to create per-player inventory nodes and update them. ```typescript // Server import { Players } from "@rbxts/services"; import { WisprToken, createNode, patchNode, pathOf, opIncrement, opListPush } from "@rbxts/wispr"; interface Inventory { gold: number; items: string[]; equipment: Record; } // Create a token factory for per-player inventories function getPlayerInventoryToken(player: Player): WisprToken { return WisprToken.create(`player.${player.UserId}.inventory`); } // Create per-player inventory when a player joins Players.PlayerAdded.Connect((player) => { const token = getPlayerInventoryToken(player); createNode( token, { kind: "player", player }, // Scope to this specific player { gold: 0, items: [], equipment: {} } ); }); // Function to add gold to a player's inventory function addGold(player: Player, amount: number) { const token = getPlayerInventoryToken(player); patchNode(token, opIncrement(pathOf("gold"), amount)); } // Function to add an item to a player's inventory function addItem(player: Player, item: string) { const token = getPlayerInventoryToken(player); patchNode(token, opListPush(pathOf("items"), item)); } ``` ### Client-Side Implementation This example shows how a client can request initial data, wait for its inventory node, and listen for changes. ```typescript // Client import { Players } from "@rbxts/services"; import { waitForNode, requestInitialData, pathOf, WisprToken } from "@rbxts/wispr"; await requestInitialData(); // Get the player's own inventory token const localPlayer = Players.LocalPlayer; if (!localPlayer) return; const inventoryToken = WisprToken.create(`player.${localPlayer.UserId}.inventory`); const inventory = await waitForNode(inventoryToken); // Display gold updates inventory.listenForChange(pathOf("gold"), (newGold) => { updateGoldUI(newGold as number); }); // Display items updates inventory.listenForChange(pathOf("items"), (newItems) => { updateItemsUI(newItems as string[]); }); ``` ``` -------------------------------- ### Install Wispr using npm Source: https://github.com/sawcyd/wispr/blob/master/README.md This command installs the Wispr package using npm. Ensure you have Node.js and npm installed on your system. ```bash npm install @rbxts/wispr ``` -------------------------------- ### Server Setup: Initialize and Update State with Wispr (TypeScript) Source: https://github.com/sawcyd/wispr/blob/master/README.md Demonstrates how to set up the server-side of Wispr. This includes defining a state token, creating a state node with initial values, and updating the state using patch operations like 'set' and 'increment'. ```typescript import { WisprToken, createNode, patchNode, pathOf } from "@rbxts/wispr"; // Define a token for your state type interface PlayerStats { gold: number; level: number; inventory: string[]; } const PLAYER_STATS = WisprToken.create("player.stats"); // Create a node (server-only) const node = createNode( PLAYER_STATS, { kind: "all" }, // Scope: replicate to all clients { gold: 0, level: 1, inventory: [] } // Initial state ); // Update state with patches patchNode(PLAYER_STATS, { type: "set", path: pathOf("gold"), value: 100 }); patchNode(PLAYER_STATS, { type: "increment", path: pathOf("level"), delta: 1 }); ``` -------------------------------- ### Player Inventory System with Wispr (Server & Client) Source: https://context7.com/sawcyd/wispr/llms.txt A comprehensive example of a player inventory system using Wispr for server and client integration. It covers full CRUD operations, reactive UI updates, and uses tokens defined in a shared file. Dependencies include `@rbxts/wispr` and `@rbxts/services`. ```typescript // === SHARED: tokens.ts === import { WisprToken } from "@rbxts/wispr"; export interface Inventory { gold: number; gems: number; items: string[]; equipment: Record; } export function getInventoryToken(userId: number): WisprToken { return WisprToken.create(`player.${userId}.inventory`); } ``` ```typescript // === SERVER: inventory-server.ts === import { Players } from "@rbxts/services"; import { createNode, destroyNode, patchNode, patchNodeMultiple, pathOf, opIncrement, opListPush, opMapSet, opMapDelete } from "@rbxts/wispr"; import { getInventoryToken, Inventory } from "./tokens"; // Initialize player inventory on join Players.PlayerAdded.Connect((player) => { const token = getInventoryToken(player.UserId); createNode( token, { kind: "player", player }, { gold: 100, gems: 0, items: ["starter_sword", "health_potion"], equipment: {} } ); }); // Cleanup on leave Players.PlayerRemoving.Connect((player) => { destroyNode(getInventoryToken(player.UserId)); }); // Game logic functions export function addGold(player: Player, amount: number) { patchNode(getInventoryToken(player.UserId), opIncrement(pathOf("gold"), amount)); } export function addItem(player: Player, item: string) { patchNode(getInventoryToken(player.UserId), opListPush(pathOf("items"), item)); } export function equipItem(player: Player, slot: string, item: { name: string; rarity: string; power: number }) { patchNode(getInventoryToken(player.UserId), opMapSet(pathOf("equipment"), slot, item)); } export function unequipItem(player: Player, slot: string) { patchNode(getInventoryToken(player.UserId), opMapDelete(pathOf("equipment"), slot)); } export function purchaseItem(player: Player, item: string, cost: number) { const token = getInventoryToken(player.UserId); patchNodeMultiple(token, [ opIncrement(pathOf("gold"), -cost), opListPush(pathOf("items"), item), ]); } ``` ```typescript // === CLIENT: inventory-client.ts === import { Players } from "@rbxts/services"; import { requestInitialData, waitForNode, pathOf } from "@rbxts/wispr"; import { getInventoryToken, Inventory } from "./tokens"; async function initializeInventoryUI() { await requestInitialData(); const localPlayer = Players.LocalPlayer; if (!localPlayer) return; const token = getInventoryToken(localPlayer.UserId); const node = await waitForNode(token); // Initial UI setup const inventory = node.getState(); updateGoldDisplay(inventory.gold); updateGemsDisplay(inventory.gems); updateItemsList(inventory.items); updateEquipmentPanel(inventory.equipment); // Reactive updates node.listenForChange(pathOf("gold"), (newGold) => { updateGoldDisplay(newGold as number); }); node.listenForChange(pathOf("gems"), (newGems) => { updateGemsDisplay(newGems as number); }); node.listenForChange(pathOf("items"), (newItems) => { updateItemsList(newItems as string[]); }); node.listenForChange(pathOf("equipment"), (newEquipment) => { updateEquipmentPanel(newEquipment as Record); }); } function updateGoldDisplay(gold: number) { /* Update gold UI element */ } function updateGemsDisplay(gems: number) { /* Update gems UI element */ } function updateItemsList(items: string[]) { /* Refresh inventory list UI */ } function updateEquipmentPanel(equipment: Record) { /* Update equipment slots UI */ } initializeInventoryUI(); ``` -------------------------------- ### WisprNode Client API: State and Events Source: https://github.com/sawcyd/wispr/blob/master/README.md Provides methods for interacting with a WisprNode on the client. Includes getting the current state, specific values, version, and listening for changes at specific paths or for any change. Also includes a destroy method for cleanup. ```typescript // Get current state (deep copy) getState(): T // Get value at a specific path getValue(path: WisprPath): unknown // Get current version getVersion(): number // Listen for changes at a specific path listenForChange(path: WisprPath, callback: (newVal: unknown, oldVal: unknown) => void): () => void // Listen for any change in the node listenForAnyChange(callback: () => void): () => void // Listen for raw patches (dev/debug) listenForRawPatch(callback: (patch: WisprPatch) => void): () => void // Destroy the node and clean up listeners destroy(): void ``` -------------------------------- ### Wait for Node Creation and Access State Source: https://context7.com/sawcyd/wispr/llms.txt The `waitForNode` function asynchronously waits for a specific Wispr data node to be created on the server. It resolves immediately if the node already exists. Once resolved, it provides methods to get the current state, specific values using `pathOf`, and the state version. ```typescript import { WisprToken, waitForNode, requestInitialData, pathOf } from "@rbxts/wispr"; import { Players } from "@rbxts/services"; interface PlayerStats { gold: number; level: number; inventory: string[]; } async function setupPlayerUI() { await requestInitialData(); const localPlayer = Players.LocalPlayer; if (!localPlayer) return; // Wait for player-specific stats node const statsToken = WisprToken.create(`player.${localPlayer.UserId}.stats`); const node = await waitForNode(statsToken); // Access current state const stats = node.getState(); print(`Gold: ${stats.gold}, Level: ${stats.level}`); // Get specific value const gold = node.getValue(pathOf("gold")) as number; print(`Current gold: ${gold}`); // Get current version const version = node.getVersion(); print(`State version: ${version}`); } setupPlayerUI(); ``` -------------------------------- ### Get Wispr Node by Token (Server/Client) Source: https://context7.com/sawcyd/wispr/llms.txt Retrieves a Wispr node using its token. This function returns undefined if the node does not exist. It's crucial to use `getServerNode` on the server and `getClientNode` on the client to access the appropriate nodes. ```typescript // Server-side import { WisprToken, createNode, getServerNode, patchNode, pathOf } from "@rbxts/wispr"; interface GameState { isRunning: boolean; currentRound: number; } const GAME = WisprToken.create("game.state"); function startGame() { // Check if node exists before creating let node = getServerNode(GAME); if (!node) { node = createNode(GAME, { kind: "all" }, { isRunning: false, currentRound: 0 }); } patchNode(GAME, { type: "set", path: pathOf("isRunning"), value: true }); } // Client-side import { WisprToken, getClientNode, requestInitialData } from "@rbxts/wispr"; async function checkGameState() { await requestInitialData(); const GAME = WisprToken.create("game.state"); const node = getClientNode(GAME); if (node) { const state = node.getState(); print(`Game running: ${state.isRunning}`); } else { print("Game state not yet available"); } } ``` -------------------------------- ### requestInitialData - Client Bootstrap Source: https://context7.com/sawcyd/wispr/llms.txt Initializes the client and requests all initial state snapshots from the server. This must be called once during client startup before accessing any nodes. ```APIDOC ## requestInitialData - Client Bootstrap ### Description Initializes the client and requests all initial state snapshots from the server. This must be called once during client startup before accessing any nodes. ### Method `requestInitialData` ### Parameters This function takes no parameters. ### Request Example ```typescript import { requestInitialData } from "@rbxts/wispr"; // Call once during client initialization async function initializeClient() { await requestInitialData(); print("Wispr client initialized - ready to access state nodes"); } initializeClient(); ``` ### Response This function is asynchronous and resolves once the initial data has been successfully fetched. ``` -------------------------------- ### Initialize Wispr Client and Request Initial Data Source: https://context7.com/sawcyd/wispr/llms.txt The `requestInitialData` function must be called once during client startup to initialize the Wispr client and fetch all initial state snapshots from the server. This function is asynchronous and should be awaited before accessing any state nodes. ```typescript import { requestInitialData } from "@rbxts/wispr"; // Call once during client initialization async function initializeClient() { await requestInitialData(); print("Wispr client initialized - ready to access state nodes"); } initializeClient(); ``` -------------------------------- ### Create Server State Nodes with createNode Source: https://context7.com/sawcyd/wispr/llms.txt Illustrates how to create server-side state nodes using the createNode function. Nodes can be configured with different replication scopes: 'all' for global visibility, 'player' for a specific client, or 'players' for a defined group. This function requires a WisprToken, a scope definition, and initial state. ```typescript import { WisprToken, createNode } from "@rbxts/wispr"; import { Players } from "@rbxts/services"; interface PlayerData { gold: number; level: number; inventory: string[]; equipment: Record; } const PLAYER_DATA = WisprToken.create("player.data"); // Create node visible to all clients const globalNode = createNode( PLAYER_DATA, { kind: "all" }, { gold: 0, level: 1, inventory: [], equipment: {} } ); // Create node visible to a single player Players.PlayerAdded.Connect((player) => { const playerToken = WisprToken.create(`player.${player.UserId}.data`); createNode( playerToken, { kind: "player", player }, { gold: 100, level: 1, inventory: ["starter_sword"], equipment: {} } ); }); // Create node visible to multiple specific players const partyMembers = [player1, player2, player3]; const partyToken = WisprToken.create<{ loot: string[] }>("party.loot"); createNode( partyToken, { kind: "players", players: partyMembers }, { loot: [] } ); ``` -------------------------------- ### Create State Tokens with WisprToken.create Source: https://context7.com/sawcyd/wispr/llms.txt Demonstrates how to create type-safe identifiers (tokens) for state nodes using WisprToken.create. Tokens should be defined at the module level for reuse and type safety. Supports defining tokens for interfaces and dynamically for specific instances like players. ```typescript import { WisprToken } from "@rbxts/wispr"; // Define your state interface interface PlayerStats { gold: number; level: number; inventory: string[]; } interface GameState { round: number; timeRemaining: number; players: Record; } // Create tokens at module level (do this once) const PLAYER_STATS = WisprToken.create("player.stats"); const GAME_STATE = WisprToken.create("game.state"); // Create per-player tokens dynamically function getPlayerInventoryToken(player: Player): WisprToken { return WisprToken.create(`player.${player.UserId}.inventory`); } ``` -------------------------------- ### Blink Integration Module Imports (JavaScript) Source: https://github.com/sawcyd/wispr/blob/master/README.md Demonstrates how Blink integration now uses module imports instead of string paths for improved type safety. This change affects how users import Blink modules directly, enhancing the robustness of the integration. ```javascript import * as serverNetwork from "server/network/network" import * as clientNetwork from "client/network/network" ``` -------------------------------- ### Use Helper Functions for Patch Operations Source: https://context7.com/sawcyd/wispr/llms.txt Helper functions like `opSet`, `opIncrement`, `opListPush`, etc., provide a more concise and readable API for constructing patch operations. They can be used with `patchNode` or `patchNodeMultiple` for applying changes. These helpers include built-in validation for operation parameters. ```typescript import { WisprToken, createNode, patchNode, patchNodeMultiple, pathOf, opSet, opDelete, opIncrement, opListPush, opListInsert, opListRemoveAt, opMapSet, opMapDelete } from "@rbxts/wispr"; interface GameData { score: number; lives: number; powerups: string[]; achievements: Record; } const GAME = WisprToken.create("game.data"); createNode(GAME, { kind: "all" }, { score: 0, lives: 3, powerups: [], achievements: {} }); // Using helper functions (cleaner syntax) patchNode(GAME, opSet(pathOf("score"), 1000)); patchNode(GAME, opIncrement(pathOf("lives"), -1)); patchNode(GAME, opListPush(pathOf("powerups"), "speed_boost")); patchNode(GAME, opMapSet(pathOf("achievements"), "first_win", { unlocked: true, timestamp: os.time() })); // Apply multiple operations atomically patchNodeMultiple(GAME, [ opSet(pathOf("score"), 0), opSet(pathOf("lives"), 3), opDelete(pathOf("powerups")), ]); ``` -------------------------------- ### Blink Integration Source: https://github.com/sawcyd/wispr/blob/master/README.md Information regarding the status and limitations of Blink integration with Wispr. ```APIDOC ## Blink Integration ### Status **Currently Unavailable** Blink integration has been temporarily disabled due to compatibility issues. Wispr currently relies on standard RemoteFunction/RemoteEvent for all networking. This feature may be re-enabled in a future release. ### Details Wispr was designed to support optional integration with [Blink](https://1axen.github.io/blink/) for enhanced performance and security. However, this feature is currently disabled. The `configureBlink()` function is available for API compatibility but will not enable Blink integration. **Wispr will always use standard RemoteFunction/RemoteEvent** regardless of any Blink configuration attempts. ``` -------------------------------- ### waitForNode - Wait for Node Creation Source: https://context7.com/sawcyd/wispr/llms.txt Waits for a node with the given token to be created and returns it. Resolves immediately if the node already exists. ```APIDOC ## waitForNode - Wait for Node Creation ### Description Waits for a node with the given token to be created and returns it. Resolves immediately if the node already exists. ### Method `waitForNode` ### Parameters - **token** (`WisprToken`): The token of the node to wait for. ### Request Example ```typescript import { WisprToken, waitForNode, requestInitialData, pathOf } from "@rbxts/wispr"; import { Players } from "@rbxts/services"; interface PlayerStats { gold: number; level: number; inventory: string[]; } async function setupPlayerUI() { await requestInitialData(); const localPlayer = Players.LocalPlayer; if (!localPlayer) return; // Wait for player-specific stats node const statsToken = WisprToken.create(`player.${localPlayer.UserId}.stats`); const node = await waitForNode(statsToken); // Access current state const stats = node.getState(); print(`Gold: ${stats.gold}, Level: ${stats.level}`); // Get specific value const gold = node.getValue(pathOf("gold")) as number; print(`Current gold: ${gold}`); // Get current version const version = node.getVersion(); print(`State version: ${version}`); } setupPlayerUI(); ``` ### Response - **node** (`Node`): The created node object, allowing access to its state, values, and version. ``` -------------------------------- ### Helper Functions for Patch Operations Source: https://context7.com/sawcyd/wispr/llms.txt Provides cleaner syntax for creating patch operations with built-in validation, including atomic application of multiple operations. ```APIDOC ## Helper Functions - opSet, opIncrement, opListPush, etc. ### Description Helper functions provide a cleaner API for creating patch operations with built-in validation. `patchNodeMultiple` allows applying multiple operations atomically. ### Method `opSet`, `opDelete`, `opIncrement`, `opListPush`, `opListInsert`, `opListRemoveAt`, `opMapSet`, `opMapDelete`, `patchNodeMultiple` ### Parameters These helper functions take specific arguments to construct patch operations, which are then passed to `patchNode` or `patchNodeMultiple`. ### Request Example ```typescript import { WisprToken, patchNode, patchNodeMultiple, pathOf, opSet, opDelete, opIncrement, opListPush, opMapSet } from "@rbxts/wispr"; interface GameData { score: number; lives: number; powerups: string[]; achievements: Record; } const GAME = WisprToken.create("game.data"); // Using helper functions (cleaner syntax) patchNode(GAME, opSet(pathOf("score"), 1000)); patchNode(GAME, opIncrement(pathOf("lives"), -1)); patchNode(GAME, opListPush(pathOf("powerups"), "speed_boost")); patchNode(GAME, opMapSet(pathOf("achievements"), "first_win", { unlocked: true, timestamp: os.time() })); // Apply multiple operations atomically patchNodeMultiple(GAME, [ opSet(pathOf("score"), 0), opSet(pathOf("lives"), 3), opDelete(pathOf("powerups")), ]); ``` ### Response These functions do not return a value but are used to construct and apply patch operations. ``` -------------------------------- ### listenForChange Source: https://context7.com/sawcyd/wispr/llms.txt Subscribes to changes at a specific path within the state tree. The callback function is executed after a patch is applied and the value at the specified path has actually changed. ```APIDOC ## listenForChange - Subscribe to Path Changes ### Description Listens for changes at a specific path in the state tree. The callback fires after patch application when the value at that path actually changes. ### Method (Implicitly called on a WisprNode instance) ### Endpoint (N/A - Method on a WisprNode) ### Parameters #### Path Parameters - **path** (string) - Required - The specific path within the state tree to listen for changes. #### Query Parameters (N/A) #### Request Body (N/A) ### Request Example ```typescript node.listenForChange(pathOf("gold"), (newGold, oldGold) => { print(`Gold changed from ${oldGold} to ${newGold}`); }); ``` ### Response #### Success Response (200) (N/A - Returns a disconnect function) #### Response Example (N/A) ``` -------------------------------- ### listenForAnyChange Source: https://context7.com/sawcyd/wispr/llms.txt Subscribes to any change within the node. The callback function is executed after any patch is applied, irrespective of which path was modified. ```APIDOC ## listenForAnyChange - Subscribe to All Changes ### Description Listens for any change in the node. The callback fires after any patch is applied, regardless of which path changed. ### Method (Implicitly called on a WisprNode instance) ### Endpoint (N/A - Method on a WisprNode) ### Parameters #### Path Parameters (N/A) #### Query Parameters (N/A) #### Request Body (N/A) ### Request Example ```typescript node.listenForAnyChange(() => { const state = node.getState(); print(`Game state updated - Round: ${state.round}, Time: ${state.timeRemaining}`); }); ``` ### Response #### Success Response (200) (N/A - Returns a disconnect function) #### Response Example (N/A) ``` -------------------------------- ### Construct State Paths with Wispr (TypeScript) Source: https://github.com/sawcyd/wispr/blob/master/README.md Demonstrates how to create paths to navigate the state tree in Wispr. Paths are represented as arrays of keys or indices, allowing for precise targeting of nested state properties. ```typescript pathOf("inventory", "items", "sword_001") // ["inventory", "items", "sword_001"] pathOf("weapons", 0, "cooldown") // ["weapons", 0, "cooldown"] ``` -------------------------------- ### Listen for Nodes Matching a Pattern in Wispr Source: https://context7.com/sawcyd/wispr/llms.txt Listens for the creation of nodes that match a given token ID pattern. This functionality is analogous to ReplicaService's ReplicaOfClassCreated and is particularly useful for managing dynamically created per-player nodes. It allows setting up listeners for individual nodes as they are created. ```typescript import { onNodeOfClassCreated, requestInitialData, pathOf, WisprNode } from "@rbxts/wispr"; interface PlayerData { name: string; score: number; team: string; } async function setupDynamicNodeListeners() { await requestInitialData(); // Listen for any player data node (matches "player.data.123", "player.data.456", etc.) const disconnect = onNodeOfClassCreated("player.data.", (node: WisprNode) => { print(`Player data node created: ${node.token.id}`); const data = node.getState() as PlayerData; print(`Player ${data.name} joined team ${data.team}`); // Set up listeners for this specific player's data node.listenForChange(pathOf("score"), (newScore) => { updateLeaderboard(node.token.id, newScore as number); }); node.listenForChange(pathOf("team"), (newTeam) => { updateTeamDisplay(node.token.id, newTeam as string); }); }); // Later: stop listening for new nodes disconnect(); } function updateLeaderboard(playerId: string, score: number) { /* ... */ } function updateTeamDisplay(playerId: string, team: string) { /* ... */ } setupDynamicNodeListeners(); ``` -------------------------------- ### Utilize Wispr Helper Functions for Patch Operations (TypeScript) Source: https://github.com/sawcyd/wispr/blob/master/README.md Demonstrates the use of helper functions provided by Wispr to simplify the creation of patch operations. These functions abstract the structure of patch objects, making state updates more concise. It also shows how to apply multiple operations simultaneously. ```typescript import { opSet, opIncrement, opListPush, opMapSet } from "@rbxts/wispr"; patchNode(PLAYER_STATS, opSet(pathOf("gold"), 100)); patchNode(PLAYER_STATS, opIncrement(pathOf("level"), 1)); patchNode(PLAYER_STATS, opListPush(pathOf("inventory"), "sword")); patchNode(PLAYER_STATS, opMapSet(pathOf("equipment"), "weapon", swordData)); // Apply multiple operations at once patchNodeMultiple(PLAYER_STATS, [ opSet(pathOf("gold"), 200), opIncrement(pathOf("level"), 1), ]); ``` -------------------------------- ### onNodeOfClassCreated Source: https://context7.com/sawcyd/wispr/llms.txt Listens for the creation of nodes that match a specified token ID pattern. This is useful for dynamically created nodes, such as per-player data. ```APIDOC ## onNodeOfClassCreated - Pattern-Based Node Listening ### Description Listens for nodes matching a token ID pattern. Similar to ReplicaService's ReplicaOfClassCreated - useful for dynamically created per-player nodes. ### Method (Static method of Wispr) ### Endpoint (N/A - Function call) ### Parameters #### Path Parameters (N/A) #### Query Parameters (N/A) #### Request Body (N/A) ### Request Example ```typescript onNodeOfClassCreated("player.data.", (node) => { print(`Player data node created: ${node.token.id}`); // ... setup listeners for this node }); ``` ### Response #### Success Response (200) (N/A - Returns a disconnect function) #### Response Example (N/A) ``` -------------------------------- ### Client API Source: https://github.com/sawcyd/wispr/blob/master/README.md Client-side functions for interacting with Wispr nodes, including requesting data, waiting for nodes, and listening for node creation. ```APIDOC ## Client API ### Description Provides client-side functions for interacting with Wispr state. ### Endpoints #### `requestInitialData(): Promise` **Description**: Requests the initial state data from the server. Should be called once on startup. **Method**: Client API **Returns**: A promise that resolves when initial data is received. #### `waitForNode(token: WisprToken): Promise>` **Description**: Waits for a specific Wispr node to be created and returns it. **Method**: Client API **Parameters**: - `token` (WisprToken): The token of the node to wait for. **Returns**: A promise that resolves with the `WisprNode` when it becomes available. #### `getClientNode(token: WisprToken): WisprNode | undefined` **Description**: Retrieves a client-side Wispr node by its token. Returns `undefined` if the node is not found. **Method**: Client API **Parameters**: - `token` (WisprToken): The token of the node to retrieve. **Returns**: The `WisprNode` if found, otherwise `undefined`. #### `onNodeOfClassCreated(pattern: string, callback: (node: WisprNode) => void): () => void` **Description**: Registers a callback to be executed when a node matching the given token pattern is created. **Method**: Client API **Parameters**: - `pattern` (string): The token pattern to match. - `callback` ((node: WisprNode) => void): The function to call when a matching node is created. **Returns**: A function to unregister the callback. ``` -------------------------------- ### Listening for Node Creation by Class Token (TypeScript) Source: https://github.com/sawcyd/wispr/blob/master/README.md Introduces the `onNodeOfClassCreated` method, which allows developers to listen for the creation of nodes that match a specific token ID pattern. This functionality is similar to ReplicaService's `ReplicaOfClassCreated` and enhances node management capabilities. ```typescript wisprClient.onNodeOfClassCreated("MyClassToken*", (node) => { console.log("Node of class MyClass created:", node); }); ``` -------------------------------- ### Server API Source: https://github.com/sawcyd/wispr/blob/master/README.md Server-side functions for managing Wispr nodes, including creation, retrieval, destruction, and patching. ```APIDOC ## Server API ### Description Provides server-side functions for managing Wispr nodes. ### Endpoints #### `createNode(token: WisprToken, scope: WisprScope, initialState: T): WisprServerNode` **Description**: Creates a new state node on the server. **Method**: Server API (Internal) **Parameters**: - `token` (WisprToken): The token identifying the node. - `scope` (WisprScope): The scope for the node. - `initialState` (T): The initial state of the node. **Returns**: The created `WisprServerNode`. #### `getServerNode(token: WisprToken): WisprServerNode | undefined` **Description**: Retrieves a server-side node by its token. **Method**: Server API (Internal) **Parameters**: - `token` (WisprToken): The token of the node to retrieve. **Returns**: The `WisprServerNode` if found, otherwise `undefined`. #### `destroyNode(token: WisprToken): void` **Description**: Destroys a node on the server. **Method**: Server API (Internal) **Parameters**: - `token` (WisprToken): The token of the node to destroy. #### `patchNode(token: WisprToken, operation: WisprPatchOp): void` **Description**: Applies a single patch operation to a node on the server. **Method**: Server API (Internal) **Parameters**: - `token` (WisprToken): The token of the node to patch. - `operation` (WisprPatchOp): The patch operation to apply. #### `patchNodeMultiple(token: WisprToken, operations: readonly WisprPatchOp[]): void` **Description**: Applies multiple patch operations to a node on the server. **Method**: Server API (Internal) **Parameters**: - `token` (WisprToken): The token of the node to patch. - `operations` (readonly WisprPatchOp[]): An array of patch operations to apply. ``` -------------------------------- ### getServerNode / getClientNode Source: https://context7.com/sawcyd/wispr/llms.txt Retrieves a specific node identified by its token. Returns undefined if the node does not exist. `getServerNode` is used on the server, and `getClientNode` is used on the client. ```APIDOC ## getServerNode / getClientNode - Get Node by Token ### Description Retrieves a node by its token. Returns undefined if the node doesn't exist. Use on server for server nodes, on client for client nodes. ### Method (Static methods of Wispr) ### Endpoint (N/A - Function call) ### Parameters #### Path Parameters (N/A) #### Query Parameters (N/A) #### Request Body (N/A) ### Request Example ```typescript // Server-side const node = getServerNode(GAME); // Client-side const node = getClientNode(GAME); ``` ### Response #### Success Response (200) - **WisprNode** - The node object if found. - **undefined** - If the node does not exist. #### Response Example ```typescript // If node exists { "token": { "id": "game.state" }, // ... other node properties } // If node does not exist undefined ``` ``` -------------------------------- ### Wispr Server API: Node Management Source: https://github.com/sawcyd/wispr/blob/master/README.md Manages Wispr nodes on the server-side. Includes functions to create, retrieve, destroy, and patch nodes. Requires WisprToken and WisprScope for node identification and management. ```typescript // Create a new state node createNode(token: WisprToken, scope: WisprScope, initialState: T): WisprServerNode // Get a node by token (server-side) getServerNode(token: WisprToken): WisprServerNode | undefined // Destroy a node destroyNode(token: WisprToken): void // Apply a single patch operation patchNode(token: WisprToken, operation: WisprPatchOp): void // Apply multiple patch operations patchNodeMultiple(token: WisprToken, operations: readonly WisprPatchOp[]): void ``` -------------------------------- ### Destroy Server Node with Wispr Source: https://context7.com/sawcyd/wispr/llms.txt Demonstrates how to destroy a server node using Wispr's `destroyNode` function. This is typically done when a player leaves, ensuring their data is cleaned up. It relies on the `@rbxts/wispr` library. ```typescript import { WisprToken, createNode, destroyNode, patchNode, pathOf } from "@rbxts/wispr"; import { Players } from "@rbxts/services"; interface PlayerData { gold: number; level: number; } // Create per-player nodes on join Players.PlayerAdded.Connect((player) => { const token = WisprToken.create(`player.${player.UserId}.data`); createNode(token, { kind: "player", player }, { gold: 0, level: 1 }); }); // Destroy per-player nodes on leave Players.PlayerRemoving.Connect((player) => { const token = WisprToken.create(`player.${player.UserId}.data`); destroyNode(token); print(`Destroyed node for player ${player.Name}`); }); ``` -------------------------------- ### Wispr Client API: Node Interaction Source: https://github.com/sawcyd/wispr/blob/master/README.md Handles client-side interactions with Wispr nodes. Allows requesting initial data, waiting for nodes, retrieving nodes, and listening for node creation events. Returns Promises for asynchronous operations. ```typescript // Request initial data (call once on startup) requestInitialData(): Promise // Wait for a node to be created waitForNode(token: WisprToken): Promise> // Get a node (returns undefined if not found) getClientNode(token: WisprToken): WisprNode | undefined // Listen for nodes matching a token ID pattern onNodeOfClassCreated(pattern: string, callback: (node: WisprNode) => void): () => void ``` -------------------------------- ### Listen for Any Change in Wispr Node Source: https://context7.com/sawcyd/wispr/llms.txt Subscribes to any change within a Wispr node. The callback function is executed whenever any patch is applied to the node, regardless of which specific path was modified. This provides a broad overview of state updates. ```typescript import { WisprToken, waitForNode, requestInitialData } from "@rbxts/wispr"; interface GameState { round: number; timeRemaining: number; scores: Record; } async function setupGameListener() { await requestInitialData(); const gameToken = WisprToken.create("game.state"); const node = await waitForNode(gameToken); // Listen for any state change const disconnect = node.listenForAnyChange(() => { const state = node.getState(); print(`Game state updated - Round: ${state.round}, Time: ${state.timeRemaining}`); refreshGameUI(state); }); // Later: disconnect listener disconnect(); } function refreshGameUI(state: { round: number; timeRemaining: number; scores: Record }) { /* update UI */ } setupGameListener(); ``` -------------------------------- ### Create State Paths with pathOf Source: https://context7.com/sawcyd/wispr/llms.txt Shows how to construct type-safe paths for navigating nested state within Wispr. The pathOf function accepts string or number keys to build an array representing the location of a state value, supporting access to array elements and map properties. ```typescript import { pathOf } from "@rbxts/wispr"; // Basic path creation const goldPath = pathOf("gold"); // ["gold"] const inventoryPath = pathOf("inventory"); // ["inventory"] const firstItemPath = pathOf("inventory", 0); // ["inventory", 0] const nestedPath = pathOf("equipment", "weapon", "power"); // ["equipment", "weapon", "power"] // Paths support both string and number keys const arrayAccess = pathOf("weapons", 0, "cooldown"); // ["weapons", 0, "cooldown"] const mapAccess = pathOf("players", "player123", "score"); // ["players", "player123", "score"] ``` -------------------------------- ### Configure State Replication Scopes with Wispr (TypeScript) Source: https://github.com/sawcyd/wispr/blob/master/README.md Explains how to define the scope of state replication in Wispr, controlling which clients receive state updates. Options include replicating to all clients, a single specific player, or a list of players. ```typescript { kind: "all" } // All clients { kind: "player", player: somePlayer } // Single player { kind: "players", players: [p1, p2] } // Multiple players ``` -------------------------------- ### WisprNode (Client) Source: https://github.com/sawcyd/wispr/blob/master/README.md Client-side WisprNode object methods for accessing state, listening for changes, and managing the node. ```APIDOC ## WisprNode (Client) ### Description Represents a Wispr node on the client-side, providing methods to interact with its state and changes. ### Methods #### `getState(): T` **Description**: Gets a deep copy of the current state of the Wispr node. **Returns**: The current state of type `T`. #### `getValue(path: WisprPath): unknown` **Description**: Gets the value at a specific path within the node's state. **Parameters**: - `path` (WisprPath): The path to the desired value. **Returns**: The value at the specified path. #### `getVersion(): number` **Description**: Gets the current version number of the Wispr node. **Returns**: The current version number. #### `listenForChange(path: WisprPath, callback: (newVal: unknown, oldVal: unknown) => void): () => void` **Description**: Listens for changes at a specific path within the node's state. **Parameters**: - `path` (WisprPath): The path to listen for changes on. - `callback` ((newVal: unknown, oldVal: unknown) => void): The function to call when a change occurs. Receives the new and old values. **Returns**: A function to unregister the listener. #### `listenForAnyChange(callback: () => void): () => void` **Description**: Listens for any change occurring within the Wispr node. **Parameters**: - `callback` (() => void): The function to call when any change occurs. **Returns**: A function to unregister the listener. #### `listenForRawPatch(callback: (patch: WisprPatch) => void): () => void` **Description**: Listens for raw patch operations applied to the node. Primarily for debugging. **Parameters**: - `callback` ((patch: WisprPatch) => void): The function to call with the raw patch data. **Returns**: A function to unregister the listener. #### `destroy(): void` **Description**: Destroys the Wispr node on the client and cleans up all associated listeners. ``` -------------------------------- ### Listen for Specific Path Changes in Wispr Source: https://context7.com/sawcyd/wispr/llms.txt Subscribes to changes at a specific path within the state tree. The callback function is invoked after a patch is applied, but only if the value at the specified path has actually changed. This is useful for reacting to granular state updates. ```typescript import { WisprToken, waitForNode, requestInitialData, pathOf } from "@rbxts/wispr"; interface PlayerStats { gold: number; level: number; health: number; inventory: string[]; } async function setupStatListeners() { await requestInitialData(); const statsToken = WisprToken.create("player.stats"); const node = await waitForNode(statsToken); // Listen for gold changes const disconnectGold = node.listenForChange(pathOf("gold"), (newGold, oldGold) => { print(`Gold changed from ${oldGold} to ${newGold}`); updateGoldUI(newGold as number); }); // Listen for level changes node.listenForChange(pathOf("level"), (newLevel, oldLevel) => { print(`Level up! ${oldLevel} -> ${newLevel}`); playLevelUpEffect(); }); // Listen for inventory changes node.listenForChange(pathOf("inventory"), (newInventory) => { updateInventoryUI(newInventory as string[]); }); // Later: disconnect the listener disconnectGold(); } function updateGoldUI(gold: number) { /* ... */ } function playLevelUpEffect() { /* ... */ } function updateInventoryUI(items: string[]) { /* ... */ } setupStatListeners(); ```