API Reference
Namespace

Namespace

The Namespace is the object returned by KravasNet.createNamespace. It accumulates pending channel definitions and only registers them with ByteNet once, at build time.

type Namespace = {
	_name  : string,
	add    : (self: Namespace, name: string, schema: {[string]: any}, options: ChannelOptions?) -> Namespace,
	build  : (self: Namespace) -> Namespace,
	[string]: Channel,
}

Properties

PropertyTypeDescription
_namestringThe name passed to createNamespace. Also the ByteNet namespace name.
[channelName]ChannelReading any channel key triggers :build() if it hasn't run yet, then returns the live Channel.

Namespace:add

Namespace:add(name: string, schema: {[string]: any}, options: ChannelOptions?): Namespace

Queues a new pending channel. Returns the namespace itself for chaining.

ParameterTypeRequiredDescription
namestringYesNon-empty channel name, unique within this namespace.
schema{[string]: any}YesField-to-type map for this channel's packet.
optionsChannelOptionsNoReliability and middleware. See Middleware & Reliability.

Errors:

ConditionMessage
name is empty or not a string"Channel name must be a non-empty string"
A channel with that name is already pendingChannel "{name}" already exists in namespace "{namespace}"
The namespace was already builtA descriptive error explaining that ByteNet requires every packet in one defineNamespace call, and this channel can't be added post-hoc
local CombatNet = KravasNet.createNamespace("Combat")
	:add("Damage", { amount = KravasNet.float32 })
	:add("Heal",   { amount = KravasNet.float32 })
🚫

Once a namespace is built, :add() throws instead of failing silently or crashing deep inside ByteNet. If you hit this error, the fix is almost always to move the :add() call earlier, before anything reads a channel off this namespace (including on the other side of the client/server boundary, and including calling :build()).

Building the namespace

Namespace:build(): Namespace

Registers every pending channel with ByteNet in a single ByteNet.defineNamespace() call, then replaces each pending entry with a live Channel. Idempotent — calling it again after the namespace is already built is a no-op.

This happens in one of two ways:

  1. Automatically, the first time any channel key is read off the namespace (e.g. CombatNet.Damage).
  2. Explicitly, if you call Namespace:build() yourself.
⚠️

Prefer calling :build() explicitly, right after your :add() calls, in the same module. If you rely on automatic building, the exact moment a namespace becomes "live" depends on which script happens to touch a channel first — and if the server and client touch it at very different times, the client can read the replicated namespace data before the server has written it (or vice versa), leading to dropped packets or runtime errors deep inside ByteNet's buffer reader. See Best Practices for the full explanation and a concrete example of this failure mode.

local CombatNet = KravasNet.createNamespace("Combat")
	:add("Damage",   { amount = KravasNet.float32 })
	:add("Position", { pos = KravasNet.vec3 }, { reliability = "unreliable" })
 
CombatNet:build()
 
return CombatNet

Immediate channels via createNamespace

The second argument to createNamespace accepts a table where each value is either:

  1. A plain schema table → queued with default options (reliable, no middleware).
  2. A PacketDef produced by KravasNet.packet(schema, options) → queued with the specified options.
local CombatNet = KravasNet.createNamespace("Combat", {
	Damage   = { amount = KravasNet.float32 },
	Position = KravasNet.packet({ pos = KravasNet.vec3 }, { reliability = "unreliable" }),
})

Both forms are just queued as pending — nothing is registered with ByteNet until :build() runs.