@axpecter/lync 1.4.3 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +41 -2
  2. package/package.json +1 -1
  3. package/src/Types.luau +13 -1
  4. package/src/api/Group.luau +35 -28
  5. package/src/api/Namespace.luau +107 -89
  6. package/src/api/Packet.luau +86 -55
  7. package/src/api/Query.luau +101 -95
  8. package/src/api/Scope.luau +32 -18
  9. package/src/api/Signal.luau +78 -56
  10. package/src/codec/Base.luau +17 -17
  11. package/src/codec/composite/Array.luau +74 -74
  12. package/src/codec/composite/Map.luau +80 -80
  13. package/src/codec/composite/Optional.luau +14 -14
  14. package/src/codec/composite/Shared.luau +35 -35
  15. package/src/codec/composite/Struct.luau +108 -108
  16. package/src/codec/composite/Tagged.luau +71 -71
  17. package/src/codec/composite/Tuple.luau +35 -35
  18. package/src/codec/datatype/Buffer.luau +18 -18
  19. package/src/codec/datatype/CFrame.luau +24 -24
  20. package/src/codec/datatype/Color.luau +8 -12
  21. package/src/codec/datatype/Instance.luau +13 -13
  22. package/src/codec/datatype/IntVector.luau +17 -17
  23. package/src/codec/datatype/NumberRange.luau +7 -7
  24. package/src/codec/datatype/Ray.luau +16 -16
  25. package/src/codec/datatype/Rect.luau +13 -13
  26. package/src/codec/datatype/Region.luau +32 -36
  27. package/src/codec/datatype/Sequence.luau +41 -41
  28. package/src/codec/datatype/String.luau +28 -28
  29. package/src/codec/datatype/UDim.luau +17 -17
  30. package/src/codec/datatype/Vector.luau +14 -18
  31. package/src/codec/meta/Auto.luau +75 -73
  32. package/src/codec/meta/Bitfield.luau +46 -46
  33. package/src/codec/meta/Custom.luau +7 -7
  34. package/src/codec/meta/Enum.luau +25 -25
  35. package/src/codec/meta/Nothing.luau +3 -3
  36. package/src/codec/meta/Quantized.luau +63 -60
  37. package/src/codec/meta/Unknown.luau +11 -11
  38. package/src/codec/primitive/Bool.luau +12 -12
  39. package/src/codec/primitive/Float16.luau +28 -28
  40. package/src/codec/primitive/Number.luau +41 -41
  41. package/src/codec/primitive/Varint.luau +19 -19
  42. package/src/index.d.ts +2 -2
  43. package/src/init.luau +81 -61
  44. package/src/internal/Baseline.luau +7 -7
  45. package/src/internal/Channel.luau +56 -56
  46. package/src/internal/Middleware.luau +29 -29
  47. package/src/internal/Pool.luau +15 -15
  48. package/src/internal/Registry.luau +19 -19
  49. package/src/transport/Bridge.luau +17 -17
  50. package/src/transport/Client.luau +46 -46
  51. package/src/transport/Gate.luau +56 -64
  52. package/src/transport/Reader.luau +34 -34
  53. package/src/transport/Server.luau +89 -89
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  ```toml
15
15
  [dependencies]
16
- Lync = "axp3cter/lync@1.4.2"
16
+ Lync = "axp3cter/lync@1.5.1"
17
17
  ```
18
18
 
19
19
  **npm (roblox-ts)**
@@ -163,7 +163,7 @@ end
163
163
  | | What it does |
164
164
  |:---------|:------------|
165
165
  | `Lync.start()` | Sets up transport. Server creates remotes, client connects. Call once after all definitions. |
166
- | `Lync.VERSION` | `"1.4.2"` |
166
+ | `Lync.VERSION` | `"1.5.1"` |
167
167
 
168
168
  ## Packets
169
169
 
@@ -228,6 +228,45 @@ packet:send(data) -- send to server
228
228
 
229
229
  `Lync.defineNamespace(name, config)` returns a Namespace. Takes a `packets` table and/or a `queries` table. All names get auto-prefixed with `"YourNamespace."` so nothing collides.
230
230
 
231
+ The config takes `PacketConfig` and `QueryConfig` objects (same shape you'd pass to `definePacket` / `defineQuery`). The namespace creates and owns the packets/queries internally.
232
+
233
+ ```luau
234
+ local Combat = Lync.defineNamespace("Combat", {
235
+ packets = {
236
+ Hit = {
237
+ value = Lync.struct({ targetId = Lync.u16, damage = Lync.f32, headshot = Lync.bool }),
238
+ rateLimit = { maxPerSecond = 30, burstAllowance = 5 },
239
+ validate = function(data, player)
240
+ if data.damage > 200 then return false, "damage" end
241
+ return true
242
+ end,
243
+ },
244
+ Death = { value = Lync.u16 },
245
+ },
246
+ queries = {
247
+ Stats = {
248
+ request = Lync.nothing,
249
+ response = Lync.struct({ kills = Lync.u16, deaths = Lync.u16 }),
250
+ timeout = 3,
251
+ },
252
+ },
253
+ })
254
+
255
+ -- Access by short name directly on the namespace
256
+ Combat.Hit:send(data, player)
257
+ Combat.Death:listen(function(targetId, sender) end)
258
+ Combat.Stats:listen(function(request, player) return { kills = 10, deaths = 2 } end)
259
+
260
+ -- Or via the typed sub-tables
261
+ Combat.packets.Hit:send(data, player)
262
+ Combat.queries.Stats:request(nil)
263
+ ```
264
+
265
+ | Config field | Type | What it does |
266
+ |:-------------|:-----|:-------------|
267
+ | `packets` | `{ [string]: PacketConfig }?` | Map of short name → packet config. Each entry becomes a Packet on the namespace. |
268
+ | `queries` | `{ [string]: QueryConfig }?` | Map of short name → query config. Each entry becomes a Query on the namespace. |
269
+
231
270
  Access packets and queries by their short name on the returned object: `ns.PacketName`, `ns.QueryName`. Or use the typed sub-tables: `ns.packets.PacketName`, `ns.queries.QueryName`.
232
271
 
233
272
  | Method | What it does |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axpecter/lync",
3
- "version": "1.4.3",
3
+ "version": "1.5.1",
4
4
  "description": "Buffer networking for Roblox. Delta compression, XOR framing, built-in security.",
5
5
  "main": "src/init.luau",
6
6
  "types": "src/index.d.ts",
package/src/Types.luau CHANGED
@@ -51,6 +51,18 @@ export type QueryConfig<Req, Resp> = {
51
51
  validate: ((data: Req, player: Player) -> (boolean, string?))?,
52
52
  }
53
53
 
54
+ export type NamespaceConfig = {
55
+ packets: { [string]: PacketConfig<any> }?,
56
+ queries: { [string]: QueryConfig<any, any> }?,
57
+ }
58
+
59
+ -- Minimal signal interface for Registration. Avoids circular dependency
60
+ -- with Signal.luau while typing the two methods consumers actually call.
61
+ export type SignalLike = {
62
+ fire: (self: SignalLike, ...any) -> (),
63
+ connect: (self: SignalLike, callback: (...any) -> ()) -> Connection,
64
+ }
65
+
54
66
  export type Registration = {
55
67
  id: number,
56
68
  name: string,
@@ -59,7 +71,7 @@ export type Registration = {
59
71
  isUnreliable: boolean,
60
72
  kind: number,
61
73
  partner: number?,
62
- signal: any,
74
+ signal: SignalLike,
63
75
  rateLimit: RateLimitConfig?,
64
76
  validate: ((data: any, player: Player) -> (boolean, string?))?,
65
77
  needsGate: boolean,
@@ -2,17 +2,17 @@
2
2
  --!optimize 2
3
3
  -- Named player sets with auto-cleanup on PlayerRemoving.
4
4
 
5
- local Players = game:GetService ("Players")
5
+ local Players = game:GetService("Players")
6
6
 
7
- -- State --------------------------------------------------------------
7
+ -- State ---------------------------------------------------------------
8
8
 
9
9
  local _groups = {} :: { [string]: { [Player]: true } }
10
10
  local _counts = {} :: { [string]: number }
11
11
  local _playerGroups = {} :: { [Player]: { [string]: true } }
12
12
 
13
- -- Private ------------------------------------------------------------
13
+ -- Private -------------------------------------------------------------
14
14
 
15
- local function onPlayerRemoving (player: Player): ()
15
+ local function onPlayerRemoving(player: Player): ()
16
16
  local memberships = _playerGroups[player]
17
17
  if not memberships then
18
18
  return
@@ -29,16 +29,23 @@ local function onPlayerRemoving (player: Player): ()
29
29
  _playerGroups[player] = nil
30
30
  end
31
31
 
32
- Players.PlayerRemoving:Connect (onPlayerRemoving)
32
+ Players.PlayerRemoving:Connect(onPlayerRemoving)
33
+
34
+ type GroupFields = {
35
+ _tag: "group",
36
+ _name: string,
37
+ }
33
38
 
34
39
  local GroupImpl = {}
35
40
  GroupImpl.__index = GroupImpl
36
41
 
37
- function GroupImpl.add (self: any, player: Player): boolean
42
+ export type Group = typeof(setmetatable({} :: GroupFields, GroupImpl))
43
+
44
+ function GroupImpl.add(self: Group, player: Player): boolean
38
45
  local name = self._name
39
46
  local set = _groups[name]
40
47
  if not set then
41
- error (`[Lync] Group has been destroyed: "{name}"`)
48
+ error(`[Lync] Group has been destroyed: "{name}"`)
42
49
  end
43
50
 
44
51
  if set[player] then
@@ -58,11 +65,11 @@ function GroupImpl.add (self: any, player: Player): boolean
58
65
  return true
59
66
  end
60
67
 
61
- function GroupImpl.remove (self: any, player: Player): boolean
68
+ function GroupImpl.remove(self: Group, player: Player): boolean
62
69
  local name = self._name
63
70
  local set = _groups[name]
64
71
  if not set then
65
- error (`[Lync] Group has been destroyed: "{name}"`)
72
+ error(`[Lync] Group has been destroyed: "{name}"`)
66
73
  end
67
74
 
68
75
  if not set[player] then
@@ -80,48 +87,48 @@ function GroupImpl.remove (self: any, player: Player): boolean
80
87
  return true
81
88
  end
82
89
 
83
- function GroupImpl.has (self: any, player: Player): boolean
90
+ function GroupImpl.has(self: Group, player: Player): boolean
84
91
  local name = self._name
85
92
  local set = _groups[name]
86
93
  if not set then
87
- error (`[Lync] Group has been destroyed: "{name}"`)
94
+ error(`[Lync] Group has been destroyed: "{name}"`)
88
95
  end
89
96
  return set[player] == true
90
97
  end
91
98
 
92
- function GroupImpl.count (self: any): number
99
+ function GroupImpl.count(self: Group): number
93
100
  local name = self._name
94
101
  if not _groups[name] then
95
- error (`[Lync] Group has been destroyed: "{name}"`)
102
+ error(`[Lync] Group has been destroyed: "{name}"`)
96
103
  end
97
104
  return _counts[name]
98
105
  end
99
106
 
100
- function GroupImpl.forEach (self: any, fn: (player: Player) -> ()): ()
107
+ function GroupImpl.forEach(self: Group, fn: (player: Player) -> ()): ()
101
108
  local name = self._name
102
109
  local set = _groups[name]
103
110
  if not set then
104
- error (`[Lync] Group has been destroyed: "{name}"`)
111
+ error(`[Lync] Group has been destroyed: "{name}"`)
105
112
  end
106
113
  for player in set do
107
- fn (player)
114
+ fn(player)
108
115
  end
109
116
  end
110
117
 
111
- function GroupImpl.getSet (self: any): { [Player]: true }
118
+ function GroupImpl.getSet(self: Group): { [Player]: true }
112
119
  local name = self._name
113
120
  local set = _groups[name]
114
121
  if not set then
115
- error (`[Lync] Group has been destroyed: "{name}"`)
122
+ error(`[Lync] Group has been destroyed: "{name}"`)
116
123
  end
117
124
  return set
118
125
  end
119
126
 
120
- function GroupImpl.destroy (self: any): ()
127
+ function GroupImpl.destroy(self: Group): ()
121
128
  local name = self._name
122
129
  local set = _groups[name]
123
130
  if not set then
124
- error (`[Lync] Group has been destroyed: "{name}"`)
131
+ error(`[Lync] Group has been destroyed: "{name}"`)
125
132
  end
126
133
 
127
134
  for player in set do
@@ -135,23 +142,23 @@ function GroupImpl.destroy (self: any): ()
135
142
  _counts[name] = nil
136
143
  end
137
144
 
138
- table.freeze (GroupImpl)
145
+ table.freeze(GroupImpl)
139
146
 
140
- -- Public -------------------------------------------------------------
147
+ -- Public --------------------------------------------------------------
141
148
 
142
- local Group = {}
149
+ local GroupModule = {}
143
150
 
144
- function Group.create (name: string): any
151
+ function GroupModule.create(name: string): Group
145
152
  if _groups[name] then
146
- error (`[Lync] Group already exists: "{name}"`)
153
+ error(`[Lync] Group already exists: "{name}"`)
147
154
  end
148
155
  _groups[name] = {}
149
156
  _counts[name] = 0
150
157
 
151
- return setmetatable ({
152
- _tag = "group",
158
+ return setmetatable({
159
+ _tag = "group" :: "group",
153
160
  _name = name,
154
161
  }, GroupImpl)
155
162
  end
156
163
 
157
- return table.freeze (Group)
164
+ return table.freeze(GroupModule)
@@ -2,57 +2,66 @@
2
2
  --!optimize 2
3
3
  -- Scoped packet/query groups with batch ops and local middleware.
4
4
 
5
- local Middleware = require (script.Parent.Parent.internal.Middleware)
6
- local Packet = require (script.Parent.Packet)
7
- local Query = require (script.Parent.Query)
8
- local Types = require (script.Parent.Parent.Types)
5
+ local Middleware = require(script.Parent.Parent.internal.Middleware)
6
+ local Packet = require(script.Parent.Packet)
7
+ local Query = require(script.Parent.Query)
8
+ local Types = require(script.Parent.Parent.Types)
9
9
 
10
10
  type Connection = Types.Connection
11
11
  type PacketConfig<T> = Types.PacketConfig<T>
12
12
  type QueryConfig<R, S> = Types.QueryConfig<R, S>
13
13
 
14
- -- Constants ----------------------------------------------------------
14
+ -- Constants -----------------------------------------------------------
15
15
 
16
16
  local MAX_NAMESPACES = 64
17
17
 
18
- -- State --------------------------------------------------------------
18
+ -- State ---------------------------------------------------------------
19
19
 
20
20
  local _registered = {} :: { [string]: true }
21
21
  local _count = 0
22
22
 
23
- -- Private ------------------------------------------------------------
23
+ -- Private -------------------------------------------------------------
24
24
 
25
- type NamespaceConfig = {
26
- packets: { [string]: PacketConfig<any> }?,
27
- queries: { [string]: QueryConfig<any, any> }?,
25
+ type NamespaceConfig = Types.NamespaceConfig
26
+
27
+ type NamespaceFields = {
28
+ _name: string,
29
+ _packets: { [string]: Packet.Packet },
30
+ _queries: { [string]: Query.Query },
31
+ _tracked: { Connection },
32
+ _removers: { () -> () },
33
+ packets: { [string]: Packet.Packet },
34
+ queries: { [string]: Query.Query },
28
35
  }
29
36
 
30
- local function prefixName (nsName: string, name: string): string
37
+ local function prefixName(nsName: string, name: string): string
31
38
  return `{nsName}.{name}`
32
39
  end
33
40
 
34
41
  -- Wraps a middleware handler so it only fires for packets in this namespace.
35
- local function scopedHandler (
42
+ local function scopedHandler(
36
43
  nsName: string,
37
44
  handler: (data: any, name: string, player: Player?) -> any?
38
45
  ): (data: any, name: string, player: Player?) -> any?
39
46
  local prefix = nsName .. "."
40
47
  local prefixLen = #prefix
41
48
 
42
- return function (data: any, name: string, player: Player?): any?
43
- if string.sub (name, 1, prefixLen) ~= prefix then
49
+ return function(data: any, name: string, player: Player?): any?
50
+ if string.sub(name, 1, prefixLen) ~= prefix then
44
51
  return data
45
52
  end
46
- return handler (data, name, player)
53
+ return handler(data, name, player)
47
54
  end
48
55
  end
49
56
 
50
57
  local NamespaceImpl = {}
51
58
  NamespaceImpl.__index = NamespaceImpl
52
59
 
60
+ export type Namespace = typeof(setmetatable({} :: NamespaceFields, NamespaceImpl))
61
+
53
62
  -- Listens on every packet in this namespace. Returns a Connection.
54
- function NamespaceImpl.listenAll (
55
- self: any,
63
+ function NamespaceImpl.listenAll(
64
+ self: Namespace,
56
65
  callback: (name: string, data: any, sender: Player?) -> ()
57
66
  ): Connection
58
67
  local connections = {} :: { Connection }
@@ -60,174 +69,183 @@ function NamespaceImpl.listenAll (
60
69
  local prefixLen = #prefix
61
70
 
62
71
  for fullName, packet in self._packets do
63
- local shortName = string.sub (fullName, prefixLen + 1)
64
- local conn = packet:listen (function (data: any, sender: Player?): ()
65
- callback (shortName, data, sender)
72
+ local shortName = string.sub(fullName, prefixLen + 1)
73
+ local conn = packet:listen(function(data: any, sender: Player?): ()
74
+ callback(shortName, data, sender)
66
75
  end)
67
- table.insert (connections, conn)
68
- table.insert (self._tracked, conn)
76
+ table.insert(connections, conn)
77
+ table.insert(self._tracked, conn)
69
78
  end
70
79
 
71
- local bundle = {
80
+ local bundle: Connection
81
+ bundle = {
72
82
  connected = true,
73
- disconnect = function (conn: Connection): ()
74
- if not conn.connected then
83
+ disconnect = function(self: Connection): ()
84
+ if not self.connected then
75
85
  return
76
86
  end
77
- conn.connected = false
87
+ self.connected = false
78
88
 
79
89
  for _, inner in connections do
80
90
  if inner.connected then
81
- inner:disconnect ()
91
+ inner:disconnect()
82
92
  end
83
93
  end
84
94
  end,
85
95
  }
86
96
 
87
- return bundle :: any
97
+ return bundle
88
98
  end
89
99
 
90
100
  -- Disconnects every listenAll connection. Does not remove scoped middleware.
91
- function NamespaceImpl.disconnectAll (self: any): ()
101
+ function NamespaceImpl.disconnectAll(self: Namespace): ()
92
102
  for _, conn in self._tracked do
93
103
  if conn.connected then
94
- conn:disconnect ()
104
+ conn:disconnect()
95
105
  end
96
106
  end
97
- table.clear (self._tracked)
107
+ table.clear(self._tracked)
98
108
  end
99
109
 
100
110
  -- Registers send middleware scoped to this namespace. Returns a remover.
101
- function NamespaceImpl.onSend (
102
- self: any,
111
+ function NamespaceImpl.onSend(
112
+ self: Namespace,
103
113
  handler: (data: any, name: string, player: Player?) -> any?
104
114
  ): () -> ()
105
- local wrapped = scopedHandler (self._name, handler)
106
- local remover = Middleware.addSend (wrapped)
107
- table.insert (self._removers, remover)
115
+ local wrapped = scopedHandler(self._name, handler)
116
+ local remover = Middleware.addSend(wrapped)
117
+ table.insert(self._removers, remover)
108
118
 
109
- return function (): ()
110
- remover ()
111
- local idx = table.find (self._removers, remover)
119
+ return function(): ()
120
+ remover()
121
+ local idx = table.find(self._removers, remover)
112
122
  if idx then
113
- table.remove (self._removers, idx)
123
+ table.remove(self._removers, idx)
114
124
  end
115
125
  end
116
126
  end
117
127
 
118
128
  -- Registers receive middleware scoped to this namespace. Returns a remover.
119
- function NamespaceImpl.onReceive (
120
- self: any,
129
+ function NamespaceImpl.onReceive(
130
+ self: Namespace,
121
131
  handler: (data: any, name: string, player: Player?) -> any?
122
132
  ): () -> ()
123
- local wrapped = scopedHandler (self._name, handler)
124
- local remover = Middleware.addReceive (wrapped)
125
- table.insert (self._removers, remover)
133
+ local wrapped = scopedHandler(self._name, handler)
134
+ local remover = Middleware.addReceive(wrapped)
135
+ table.insert(self._removers, remover)
126
136
 
127
- return function (): ()
128
- remover ()
129
- local idx = table.find (self._removers, remover)
137
+ return function(): ()
138
+ remover()
139
+ local idx = table.find(self._removers, remover)
130
140
  if idx then
131
- table.remove (self._removers, idx)
141
+ table.remove(self._removers, idx)
132
142
  end
133
143
  end
134
144
  end
135
145
 
136
146
  -- Kills all listeners and removes all scoped middleware. Full cleanup.
137
- function NamespaceImpl.destroy (self: any): ()
138
- self:disconnectAll ()
147
+ function NamespaceImpl.destroy(self: Namespace): ()
148
+ self:disconnectAll()
139
149
 
140
150
  for _, remover in self._removers do
141
- remover ()
151
+ remover()
142
152
  end
143
- table.clear (self._removers)
153
+ table.clear(self._removers)
144
154
  end
145
155
 
146
- -- Returns every packet name (without prefix) in this namespace.
147
- function NamespaceImpl.packetNames (self: any): { string }
156
+ -- Returns every packet name(without prefix) in this namespace.
157
+ function NamespaceImpl.packetNames(self: Namespace): { string }
148
158
  local result = {} :: { string }
149
159
  local prefix = self._name .. "."
150
160
  local prefixLen = #prefix
151
161
 
152
162
  for fullName in self._packets do
153
- table.insert (result, string.sub (fullName, prefixLen + 1))
163
+ table.insert(result, string.sub(fullName, prefixLen + 1))
154
164
  end
155
165
 
156
- table.sort (result)
166
+ table.sort(result)
157
167
  return result
158
168
  end
159
169
 
160
- -- Returns every query name (without prefix) in this namespace.
161
- function NamespaceImpl.queryNames (self: any): { string }
170
+ -- Returns every query name(without prefix) in this namespace.
171
+ function NamespaceImpl.queryNames(self: Namespace): { string }
162
172
  local result = {} :: { string }
163
173
  local prefix = self._name .. "."
164
174
  local prefixLen = #prefix
165
175
 
166
176
  for fullName in self._queries do
167
- table.insert (result, string.sub (fullName, prefixLen + 1))
177
+ table.insert(result, string.sub(fullName, prefixLen + 1))
168
178
  end
169
179
 
170
- table.sort (result)
180
+ table.sort(result)
171
181
  return result
172
182
  end
173
183
 
174
- table.freeze (NamespaceImpl)
184
+ table.freeze(NamespaceImpl)
175
185
 
176
- -- Public -------------------------------------------------------------
186
+ -- Public --------------------------------------------------------------
177
187
 
178
- local Namespace = {}
188
+ local NamespaceModule = {}
179
189
 
180
190
  -- Defines a namespace. Names are auto-prefixed to prevent collisions.
181
- function Namespace.define (name: string, config: NamespaceConfig): any
191
+ function NamespaceModule.define(name: string, config: NamespaceConfig): Namespace
182
192
  if #name == 0 then
183
- error ("[Lync] Namespace name must not be empty")
193
+ error("[Lync] Namespace name must not be empty")
184
194
  end
185
195
  if _registered[name] then
186
- error (`[Lync] Duplicate namespace: "{name}"`)
196
+ error(`[Lync] Duplicate namespace: "{name}"`)
187
197
  end
188
198
  if _count >= MAX_NAMESPACES then
189
- error (`[Lync] Namespace limit reached: {MAX_NAMESPACES}`)
199
+ error(`[Lync] Namespace limit reached: {MAX_NAMESPACES}`)
190
200
  end
191
201
 
192
202
  _count += 1
193
203
  _registered[name] = true
194
204
 
195
- local packets = {} :: { [string]: any }
196
- local queries = {} :: { [string]: any }
197
- local packetsByShort = {} :: { [string]: any }
198
- local queriesByShort = {} :: { [string]: any }
199
- local fields = {} :: { [string]: any }
200
-
201
- fields._name = name
202
- fields._packets = packets
203
- fields._queries = queries
204
- fields._tracked = {} :: { Connection }
205
- fields._removers = {} :: { () -> () }
205
+ local packets = {} :: { [string]: Packet.Packet }
206
+ local queries = {} :: { [string]: Query.Query }
207
+ local packetsByShort = {} :: { [string]: Packet.Packet }
208
+ local queriesByShort = {} :: { [string]: Query.Query }
206
209
 
207
210
  if config.packets then
208
211
  for shortName, packetConfig in config.packets do
209
- local fullName = prefixName (name, shortName)
210
- local packet = Packet.define (fullName, packetConfig)
212
+ local fullName = prefixName(name, shortName)
213
+ local packet = Packet.define(fullName, packetConfig)
211
214
  packets[fullName] = packet
212
215
  packetsByShort[shortName] = packet
213
- fields[shortName] = packet
214
216
  end
215
217
  end
216
218
 
217
219
  if config.queries then
218
220
  for shortName, queryConfig in config.queries do
219
- local fullName = prefixName (name, shortName)
220
- local query = Query.define (fullName, queryConfig)
221
+ local fullName = prefixName(name, shortName)
222
+ local query = Query.define(fullName, queryConfig)
221
223
  queries[fullName] = query
222
224
  queriesByShort[shortName] = query
223
- fields[shortName] = query
224
225
  end
225
226
  end
226
227
 
227
- fields.packets = table.freeze (packetsByShort)
228
- fields.queries = table.freeze (queriesByShort)
228
+ local nsFields: NamespaceFields = {
229
+ _name = name,
230
+ _packets = packets,
231
+ _queries = queries,
232
+ _tracked = {} :: { Connection },
233
+ _removers = {} :: { () -> () },
234
+ packets = table.freeze(packetsByShort),
235
+ queries = table.freeze(queriesByShort),
236
+ }
237
+
238
+ -- Inject dynamic short-name accessors (ns.Hit, ns.Stats, etc.)
239
+ -- These bypass the typed fields; consumers should prefer ns.packets.X / ns.queries.X.
240
+ local raw = nsFields :: any
241
+ for shortName, packet in packetsByShort do
242
+ raw[shortName] = packet
243
+ end
244
+ for shortName, query in queriesByShort do
245
+ raw[shortName] = query
246
+ end
229
247
 
230
- return setmetatable (fields, NamespaceImpl)
248
+ return setmetatable(nsFields, NamespaceImpl)
231
249
  end
232
250
 
233
- return table.freeze (Namespace)
251
+ return table.freeze(NamespaceModule)