{"id":"vynx777/networker","name":"networker","scope":"vynx777","platform":"roblox","description":"Namespace-based networking layer for Roblox. Routes all communication via dispatch tables, eliminating stacked connections.","version":"1.0.8","latest":"1.0.8","versions":["0.0.1","0.0.2","0.0.3","0.0.4","1.0.0","1.0.1","1.0.2","1.0.3","1.0.4","1.0.5","1.0.6","1.0.7","1.0.8"],"license":"MIT","licenseRating":"safe","licenseCaveats":["The package archive does not include its license text; the license is declared in its manifest metadata."],"licenseVerified":false,"dependencies":{"evaera/promise":{"version":"^4.0.0","alias":"Promise"}},"integrity":"0b1aac6834efbc4cd346dcf04c6008daa2a38003955567744d40db3a4146f661","likes":0,"downloads":0,"install":"forest install vynx777/networker","url":"https://forest.dev/p/roblox/vynx777/networker","files":"https://api.forest.dev/ai/package/roblox/vynx777/networker/files","readme":"# Networker\r\n> Namespace-based networking layer for Roblox, built on top of `RemoteEvent` and `RemoteFunction`.\r\n\r\nInstead of managing individual remotes per action, Networker routes all communication through a **single RemoteEvent and RemoteFunction per namespace** using internal dispatch tables — keeping your remote hierarchy clean and eliminating stacked connections.\r\n\r\nIncludes a built-in **ServiceBridge** for server-to-server communication between services, without cross-namespace client dependencies.\r\n\r\n---\r\n\r\n## Installation\r\n\r\n**Via Wally:**\r\n```toml\r\n[dependencies]\r\nNetworker = \"vynx777/networker@0.0.4\"\r\n```\r\nhttps://wally.run/package/vynx777/networker?version=0.0.4\r\n\r\n**Manual:**\r\nPlace the module in `ReplicatedStorage` and require it from any script.\r\n\r\n---\r\n\r\n## Core Concepts\r\n\r\nEach namespace exposes three methods:\r\n\r\n| Method | Side | Description |\r\n|--------|------|-------------|\r\n| `.on()` | Server / Client | Register a handler for an action |\r\n| `.send()` | Client only | Send an action to the server |\r\n| `.push()` | Server only | Send an action to one or more clients |\r\n\r\nCommunication is always **one namespace, one RemoteEvent, one RemoteFunction** — no matter how many actions you register.\r\n\r\nFor server-to-server communication, use the built-in **ServiceBridge** via `Networker.bridge`.\r\n\r\n---\r\n\r\n## API Reference\r\n\r\n### `Networker.set(namespace: string)`\r\nCreates or retrieves a networker instance for a given namespace.\r\n```luau\r\nlocal Networker = require(ReplicatedStorage.Networker)\r\nlocal shopNet = Networker.set(\"shop\")\r\n```\r\n\r\n---\r\n\r\n### `.on(actionName, fn, options?)`\r\nRegisters a handler for an incoming action.\r\n\r\n```luau\r\n-- Server: receives player + data\r\nshopNet.on(\"buyItem\", function(player: Player, data: { any }?)\r\n    print(player.Name, \"wants to buy\", data.itemId)\r\nend)\r\n\r\n-- Server TwoWay: must return a value\r\nshopNet.on(\"getItemPrice\", function(player: Player, data: { any }?)\r\n    return 100\r\nend, { TwoWay = true })\r\n\r\n-- Client: receives data only\r\nshopNet.on(\"updateShop\", function(data: { any }?)\r\n    print(\"Shop updated:\", data)\r\nend)\r\n```\r\n\r\n> ⚠️ Registering the same `actionName` twice in the same namespace will throw an error.\r\n\r\n---\r\n\r\n### `.send(actionName, data?, options?)`\r\nSends an action from the **client to the server**.\r\n\r\n```luau\r\n-- One-way: fire and forget\r\nshopNet.send(\"buyItem\", { itemId = \"pet_egg\" })\r\n\r\n-- Two-way: waits for server response\r\nlocal price = shopNet.send(\"getItemPrice\", { itemId = \"pet_egg\" }, { TwoWay = true })\r\nprint(\"Price is:\", price)\r\n```\r\n\r\n---\r\n\r\n### `.push(actionName, target, data?)`\r\nSends an action from the **server to one or more clients**.\r\n\r\n```luau\r\n-- Single player\r\nshopNet.push(\"updateShop\", player, { items = {} })\r\n\r\n-- List of players\r\nshopNet.push(\"updateShop\", { player1, player2 }, { items = {} })\r\n\r\n-- Everyone\r\nshopNet.push(\"updateShop\", \"all\", { items = {} })\r\n```\r\n\r\n---\r\n\r\n## ServiceBridge\r\n\r\nServiceBridge is a **server-only** communication layer between services. Instead of having clients listen to foreign namespaces, all inter-service communication stays on the server.\r\n\r\n### Why it exists\r\n\r\n```luau\r\n-- ❌ Wrong: InventoryClient secretly listening to shop namespace\r\nlocal shopNet = Networker.set(\"shop\")\r\nshopNet.on(\"itemBought\", function(data) end)  -- hidden dependency\r\n\r\n-- ✅ Correct: ShopServer talks directly to InventoryServer via bridge\r\nNetworker.bridge.fire(\"InventoryService\", \"addItem\", player, itemId)\r\n```\r\n\r\n### `Networker.bridge.on(serviceName, actionName, fn)`\r\nRegisters a handler on a service. Other services can call it via `.fire()` or `.invoke()`.\r\n\r\n```luau\r\nNetworker.bridge.on(\"InventoryService\", \"addItem\", function(player: Player, itemId: string)\r\n    -- add item to inventory\r\nend)\r\n\r\nNetworker.bridge.on(\"InventoryService\", \"hasSpace\", function(player: Player): boolean\r\n    return #getInventory(player) < MAX_SLOTS\r\nend)\r\n```\r\n\r\n### `Networker.bridge.fire(serviceName, actionName, ...)`\r\nCalls a one-way action on a service. No return value expected.\r\n\r\n```luau\r\nNetworker.bridge.fire(\"InventoryService\", \"addItem\", player, \"pet_egg\")\r\n```\r\n\r\n### `Networker.bridge.invoke(serviceName, actionName, ...)`\r\nCalls a two-way action on a service and waits for its return value.\r\n\r\n```luau\r\nlocal hasSpace = Networker.bridge.invoke(\"InventoryService\", \"hasSpace\", player)\r\nif hasSpace then\r\n    Networker.bridge.fire(\"InventoryService\", \"addItem\", player, \"pet_egg\")\r\nend\r\n```\r\n\r\n> ⚠️ All bridge methods are server-only. Calling them from the client will throw an error.\r\n\r\n---\r\n\r\n## Full Example\r\n\r\n### Server\r\n```luau\r\n-- ServerScriptService/ShopServer.luau\r\nlocal Networker = require(ReplicatedStorage.Networker)\r\n\r\nlocal shopNet = Networker.set(\"shop\")\r\n\r\nlocal PRICES = {\r\n    pet_egg = 100,\r\n    speed_upgrade = 250,\r\n}\r\n\r\n-- player asks for price\r\nshopNet.on(\"getItemPrice\", function(player: Player, data: { any }?)\r\n    return PRICES[data.itemId] or 0\r\nend, { TwoWay = true })\r\n\r\n-- player wants to buy something\r\nshopNet.on(\"buyItem\", function(player: Player, data: { any }?)\r\n    local itemId = data.itemId\r\n    local price = PRICES[itemId]\r\n\r\n    if not price then\r\n        warn(\"Unknown item:\", itemId)\r\n        return\r\n    end\r\n\r\n    -- check inventory space before doing anything\r\n    local hasSpace = Networker.bridge.invoke(\"InventoryService\", \"hasSpace\", player)\r\n    if not hasSpace then\r\n        shopNet.push(\"purchaseFailed\", player, { reason = \"Inventory full\" })\r\n        return\r\n    end\r\n\r\n    -- tell InventoryServer to add the item\r\n    Networker.bridge.fire(\"InventoryService\", \"addItem\", player, itemId)\r\n\r\n    -- tell EconomyServer to deduct coins\r\n    Networker.bridge.fire(\"EconomyService\", \"deductCoins\", player, price)\r\n\r\n    -- notify the client\r\n    shopNet.push(\"purchaseSuccess\", player, { itemId = itemId })\r\nend)\r\n```\r\n\r\n```luau\r\n-- ServerScriptService/InventoryServer.luau\r\nlocal Networker = require(ReplicatedStorage.Networker)\r\n\r\nlocal inventoryNet = Networker.set(\"inventory\")\r\n\r\n-- register handlers for other services to call\r\nNetworker.bridge.on(\"InventoryService\", \"hasSpace\", function(player: Player): boolean\r\n    return #getInventory(player) < MAX_SLOTS\r\nend)\r\n\r\nNetworker.bridge.on(\"InventoryService\", \"addItem\", function(player: Player, itemId: string)\r\n    addToInventory(player, itemId)\r\n\r\n    -- notify only THIS service's own client\r\n    inventoryNet.push(\"syncInventory\", player, { slots = getInventory(player) })\r\nend)\r\n```\r\n\r\n### Client\r\n```luau\r\n-- StarterPlayerScripts/ShopClient.luau\r\nlocal Networker = require(ReplicatedStorage.Networker)\r\n\r\nlocal shopNet = Networker.set(\"shop\")\r\n\r\nlocal price = shopNet.send(\"getItemPrice\", { itemId = \"pet_egg\" }, { TwoWay = true })\r\nprint(\"Pet Egg costs:\", price)\r\n\r\nshopNet.send(\"buyItem\", { itemId = \"pet_egg\" })\r\n\r\nshopNet.on(\"purchaseSuccess\", function(data: { any }?)\r\n    print(\"Successfully bought:\", data.itemId)\r\nend)\r\n\r\nshopNet.on(\"purchaseFailed\", function(data: { any }?)\r\n    print(\"Purchase failed:\", data.reason)\r\nend)\r\n```\r\n\r\n```luau\r\n-- StarterPlayerScripts/InventoryClient.luau\r\nlocal Networker = require(ReplicatedStorage.Networker)\r\n\r\nlocal inventoryNet = Networker.set(\"inventory\")\r\n\r\n-- only ever listens to its own namespace\r\ninventoryNet.on(\"syncInventory\", function(data: { any }?)\r\n    updateInventoryUI(data.slots)\r\nend)\r\n```\r\n\r\n---\r\n\r\n## Architecture\r\n\r\n```\r\nClient                          Server\r\n  │                               │\r\n  │── shopNet.send(\"buyItem\") ───►│\r\n  │                               │── ShopServer handles it\r\n  │                               │       │\r\n  │                               │   bridge.invoke(\"InventoryService\", \"hasSpace\")\r\n  │                               │       │\r\n  │                               │   InventoryServer returns true/false\r\n  │                               │       │\r\n  │                               │   bridge.fire(\"InventoryService\", \"addItem\")\r\n  │                               │       │\r\n  │                               │   InventoryServer adds item\r\n  │                               │       │\r\n  │◄── inventoryNet (\"syncInventory\") ────┘\r\n  │◄── shopNet (\"purchaseSuccess\") ───────┘\r\n```\r\n\r\n**One namespace, one remote:**\r\n```\r\nReplicatedStorage/\r\n  Networker/\r\n    _remotes/\r\n      shop/\r\n        RemoteEvent      ← all one-way actions for \"shop\"\r\n        RemoteFunction   ← all two-way actions for \"shop\"\r\n      inventory/\r\n        RemoteEvent\r\n        RemoteFunction\r\n```\r\n\r\n---\r\n\r\n## Action Naming Convention\r\n\r\n| Pattern | Direction | Type | Example |\r\n|---------|-----------|------|---------|\r\n| `verbNoun` | Client → Server | One-way | `buyItem` `equipPet` `feedBrainrot` |\r\n| `verbNoun` | Server → Client | One-way | `updateCoins` `syncInventory` |\r\n| `getX` `checkX` `fetchX` | Client → Server | Two-way | `getItemPrice` `checkInventorySpace` |\r\n| `verbNoun` | bridge `.fire()` | One-way | `addItem` `deductCoins` |\r\n| `hasX` `checkX` `getX` | bridge `.invoke()` | Two-way | `hasSpace` `getLevel` |\r\n\r\n---\r\n\r\n## Error Handling\r\n\r\nAll remote calls are wrapped in `pcall`. Errors propagate with full context:\r\n\r\n```\r\n[Networker] 'shop:buyItem' -> attempt to index nil value 'itemId'\r\n[ServiceBridge] 'InventoryService:addItem' -> attempt to index nil value 'player'\r\n```\r\n\r\nUnhandled actions produce a warning instead of a silent failure:\r\n```\r\n[Networker] 'shop' received unhandled action: 'unknownAction'\r\n```\r\n\r\n---\r\n\r\n## License\r\nMIT — free to use in any Roblox project.\r\n\r\n---\r\n\r\n*Made by vynx777*\r\n","readmeBorrowedFrom":{"version":"0.0.4","source":"wally"},"readmeTruncated":false}