npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rbxts/wispr

v1.0.39

Published

Server-authoritative state replication system for Roblox TypeScript

Readme

Wispr

Server-authoritative state replication system for Roblox TypeScript

Wispr provides a simple, safe, and explicit way to replicate state from server to clients using a snapshot + patch model. Built for roblox-ts.

Need help or found a bug? Join the Discord and ask:

Features

  • Server Authoritative: Only the server can mutate state, ensuring security and consistency
  • Type Safe: Full TypeScript support with type inference
  • Efficient: Snapshot + patch model minimizes bandwidth usage
  • Scoped Replication: Control which clients see which state nodes
  • Versioned Patches: Automatic handling of out-of-order patches
  • Observable: Listen to changes at specific paths or any change in a node

Installation

npm install @rbxts/wispr

Quick Start

Server Setup

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<PlayerStats>("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 });

Client Setup

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);
	});
});

Core Concepts

Tokens

Tokens are unique identifiers for state nodes. Create them once at module level:

const PLAYER_STATS = WisprToken.create<PlayerStats>("player.stats");
const GAME_STATE = WisprToken.create<GameState>("game.state");

Paths

Paths are arrays of keys (never strings) that navigate the state tree:

pathOf("inventory", "items", "sword_001")  // ["inventory", "items", "sword_001"]
pathOf("weapons", 0, "cooldown")           // ["weapons", 0, "cooldown"]

Scopes

Control which clients receive state updates:

{ kind: "all" }                           // All clients
{ kind: "player", player: somePlayer }    // Single player
{ kind: "players", players: [p1, p2] }    // Multiple players

Patch Operations

Wispr supports various patch operations:

// Set a value
{ type: "set", path: pathOf("gold"), value: 100 }

// Delete a value
{ type: "delete", path: pathOf("oldField") }

// Increment a number
{ type: "increment", path: pathOf("level"), delta: 1 }

// Array operations
{ type: "listPush", path: pathOf("inventory"), value: "sword" }
{ type: "listInsert", path: pathOf("inventory"), index: 0, value: "potion" }
{ type: "listRemoveAt", path: pathOf("inventory"), index: 2 }

// Map operations
{ type: "mapSet", pathToMap: pathOf("players"), id: "player123", value: playerData }
{ type: "mapDelete", pathToMap: pathOf("players"), id: "player123" }

Helper Functions

Wispr provides helper functions for creating patch operations:

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),
]);

API Reference

Server API

// Create a new state node
createNode<T>(token: WisprToken<T>, scope: WisprScope, initialState: T): WisprServerNode<T>

// Get a node by token (server-side)
getServerNode<T>(token: WisprToken<T>): WisprServerNode<T> | undefined

// Destroy a node
destroyNode(token: WisprToken<unknown>): void

// Apply a single patch operation
patchNode(token: WisprToken<unknown>, operation: WisprPatchOp): void

// Apply multiple patch operations
patchNodeMultiple(token: WisprToken<unknown>, operations: readonly WisprPatchOp[]): void

Client API

// Request initial data (call once on startup)
requestInitialData(): Promise<void>

// Wait for a node to be created
waitForNode<T>(token: WisprToken<T>): Promise<WisprNode<T>>

// Get a node (returns undefined if not found)
getClientNode<T>(token: WisprToken<T>): WisprNode<T> | undefined

// Listen for nodes matching a token ID pattern
onNodeOfClassCreated(pattern: string, callback: (node: WisprNode) => void): () => void

WisprNode (Client)

// 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

Architecture

Wispr uses a snapshot + patch model:

  1. Initial State: When a node is created, clients receive a complete snapshot
  2. Updates: Subsequent changes are sent as small, explicit patch operations
  3. Versioning: Each patch has a version number; out-of-order patches are ignored
  4. Scoping: The server determines which clients receive which updates based on scope

This design ensures:

  • Efficiency: Only changes are transmitted, not full state
  • Security: Server is the single source of truth
  • Reliability: Version numbers handle network issues gracefully

Example: Player Inventory

// Server
import { Players } from "@rbxts/services";
import { WisprToken, createNode, patchNode, pathOf, opIncrement, opListPush } from "@rbxts/wispr";

interface Inventory {
	gold: number;
	items: string[];
	equipment: Record<string, EquipmentData>;
}

// Create a token factory for per-player inventories
function getPlayerInventoryToken(player: Player): WisprToken<Inventory> {
	return WisprToken.create<Inventory>(`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));
}
// 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<Inventory>(`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[]);
});

Blink Integration

🚫 Currently Unavailable: Blink integration has been temporarily disabled due to compatibility issues. Wispr currently uses standard RemoteFunction/RemoteEvent for all networking. Blink integration may be re-enabled in a future release once the issues are resolved.

Wispr was designed to support optional integration with Blink for enhanced performance and security, but 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.

Future Implementations

Planned features and improvements for future releases:

Networking Enhancements

  • Reliable and Unreliable Events: Support for both reliable and unreliable event types, allowing developers to choose the appropriate reliability level for different types of updates (e.g., unreliable for frequent position updates, reliable for critical state changes)
  • Batch Updates: Send multiple patch operations in a single network message to reduce overhead for rapid state changes
  • Delta Compression: Further optimize bandwidth by only sending the differences between consecutive state snapshots
  • Custom Serialization: Allow developers to provide custom serialization/deserialization functions for specific data types

Performance Optimizations

  • Client-Side Prediction: Support for optimistic updates on the client before server confirmation, reducing perceived latency
  • Selective Replication: Fine-grained control over which parts of state are replicated to which clients based on distance, visibility, or custom logic
  • Compression Options: Configurable compression algorithms and levels for different use cases

Developer Experience

  • Middleware/Hooks System: Intercept and modify patches before they're sent or applied, enabling custom validation, logging, or transformation
  • Rate Limiting Per Node: Configure rate limits on a per-node basis to prevent abuse and manage bandwidth
  • Automatic Reconnection Handling: Automatic recovery from network interruptions with state synchronization
  • Dev Tools: Built-in debugging utilities, network traffic visualization, and state inspection tools
  • Metrics and Analytics: Built-in performance metrics for bandwidth usage, patch frequency, and replication efficiency

Changelog

[1.0.38] - 2026-01-06

  • Added: Reliable and Unreliable networking for optimized bandwidth usage
    • UnreliableRemoteEvent support for high-frequency updates (positions, animations)
    • RemoteEvent for guaranteed delivery of critical updates (inventory, quest completion)
    • Operations now accept optional reliability: "reliable" | "unreliable" field
  • Added: New streamlined Node API for server-side mutations
    • node.set(path, value, reliability?) - Set a value
    • node.increment(path, delta, reliability?) - Increment a numeric value
    • node.delete(path, reliability?) - Delete a value
    • node.insert(path, index, value, reliability?) - Insert into array
    • node.remove(path, index, reliability?) - Remove from array
    • Convenience methods: setReliable(), setUnreliable(), incrementReliable(), etc.
  • Added: Helper functions for creating operations with explicit reliability
    • opSetReliable() / opSetUnreliable()
    • opIncrementReliable() / opIncrementUnreliable()
  • Changed: patchNode() now supports reliability field in operations (backward compatible)
  • Added: getUnreliableRemoteEvent() function for creating UnreliableRemoteEvent instances
  • Note: New remotes: STATE_UPDATES_RELIABLE and STATE_UPDATES_UNRELIABLE

[1.0.35] - 2025-01-05

  • Disabled: Blink integration has been temporarily disabled due to compatibility issues. The configureBlink() function remains for API compatibility but will not enable Blink integration. Wispr will always use standard RemoteFunction/RemoteEvent.

[1.0.34] - 2025-01-05

  • Changed: Blink integration now uses module imports instead of string paths for better type safety
    • Changed serverBlinkPath and clientBlinkPath to serverBlinkModule and clientBlinkModule
    • Users now import Blink modules directly: import * as serverNetwork from "server/network/network"
    • Improves type safety and eliminates path resolution issues

[1.0.33] - 2025-01-05

  • Added: Blink integration for enhanced performance and security
    • Optional integration with Blink IDL compiler
    • Support for custom casing conventions (Pascal, Camel, Snake, Kebab)
    • Automatic fallback to standard remotes if Blink is not configured
    • See Blink Integration section for setup instructions

[1.0.32] - 2025-01-04

  • Added: onNodeOfClassCreated method for listening to nodes matching a token ID pattern (similar to ReplicaService's ReplicaOfClassCreated)

[1.0.31] - 2025-01-05

  • Added: Enhanced error handling and validation in WisprClient for better reliability and debugging

[1.0.3] - 2025-01-04

  • Added: Comprehensive error handling and input validation across all modules and classes
  • Added: Detailed error messages for better debugging
  • Added: Parameter validation for all public APIs
  • Added: Type checking and validation for paths, operations, tokens, and scopes

[1.0.2] - 2025-01-04

  • Fixed: Added missing exports for getRemoteFunction, getRemoteEvent, WISPR_REMOTES, and initializeRemotes from WisprRemotes

[1.0.1] - 2025-01-03

  • Initial release

Support

Thank you for the support!
Wispr is free to use, and your tip helps me keep maintaining and improving it. I appreciate you.

Ko-fi

License

ISC License

Copyright (c) 2026, sawcy

Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.