@axpecter/lync 2.1.2 → 2.2.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 (56) hide show
  1. package/README.md +241 -349
  2. package/package.json +1 -1
  3. package/src/Types.luau +53 -13
  4. package/src/api/Group.luau +41 -51
  5. package/src/api/Packet.luau +163 -152
  6. package/src/api/Query.luau +224 -211
  7. package/src/api/Scope.luau +36 -38
  8. package/src/api/Signal.luau +68 -94
  9. package/src/codec/Base.luau +43 -23
  10. package/src/codec/composite/Array.luau +161 -152
  11. package/src/codec/composite/Map.luau +37 -53
  12. package/src/codec/composite/Optional.luau +15 -21
  13. package/src/codec/composite/Shared.luau +78 -41
  14. package/src/codec/composite/Struct.luau +64 -125
  15. package/src/codec/composite/Tagged.luau +15 -25
  16. package/src/codec/composite/Tuple.luau +15 -20
  17. package/src/codec/datatype/Buffer.luau +44 -51
  18. package/src/codec/datatype/CFrame.luau +72 -104
  19. package/src/codec/datatype/Color.luau +14 -10
  20. package/src/codec/datatype/Instance.luau +32 -42
  21. package/src/codec/datatype/IntVector.luau +24 -22
  22. package/src/codec/datatype/NumberRange.luau +21 -12
  23. package/src/codec/datatype/Ray.luau +13 -7
  24. package/src/codec/datatype/Rect.luau +13 -7
  25. package/src/codec/datatype/Region.luau +25 -22
  26. package/src/codec/datatype/Sequence.luau +144 -118
  27. package/src/codec/datatype/String.luau +29 -59
  28. package/src/codec/datatype/UDim.luau +29 -30
  29. package/src/codec/datatype/Vector.luau +89 -105
  30. package/src/codec/meta/Auto.luau +194 -246
  31. package/src/codec/meta/Bitfield.luau +37 -36
  32. package/src/codec/meta/Custom.luau +10 -10
  33. package/src/codec/meta/Enum.luau +8 -11
  34. package/src/codec/meta/Float.luau +16 -20
  35. package/src/codec/meta/Nothing.luau +2 -2
  36. package/src/codec/meta/Unknown.luau +22 -32
  37. package/src/codec/primitive/Bool.luau +9 -5
  38. package/src/codec/primitive/Float16.luau +13 -23
  39. package/src/codec/primitive/Int.luau +20 -28
  40. package/src/codec/primitive/Number.luau +6 -10
  41. package/src/codec/primitive/Signed.luau +32 -0
  42. package/src/codec/primitive/Varint.luau +59 -28
  43. package/src/index.d.ts +8 -2
  44. package/src/init.luau +133 -143
  45. package/src/internal/Baseline.luau +17 -18
  46. package/src/internal/Channel.luau +143 -212
  47. package/src/internal/Middleware.luau +37 -47
  48. package/src/internal/Pool.luau +10 -10
  49. package/src/internal/Registry.luau +38 -34
  50. package/src/internal/Transport.luau +28 -0
  51. package/src/internal/Util.luau +26 -0
  52. package/src/transport/Bridge.luau +32 -24
  53. package/src/transport/Client.luau +51 -53
  54. package/src/transport/Gate.luau +183 -113
  55. package/src/transport/Reader.luau +95 -66
  56. package/src/transport/Server.luau +130 -125
@@ -5,275 +5,285 @@
5
5
  local Players = game:GetService("Players")
6
6
  local RunService = game:GetService("RunService")
7
7
 
8
- local Baseline = require(script.Parent.Parent.internal.Baseline)
9
8
  local Channel = require(script.Parent.Parent.internal.Channel)
10
9
  local Registry = require(script.Parent.Parent.internal.Registry)
10
+ local Transport = require(script.Parent.Parent.internal.Transport)
11
11
  local Types = require(script.Parent.Parent.Types)
12
+ local Util = require(script.Parent.Parent.internal.Util)
12
13
 
13
- -- Constants -----------------------------------------------------------
14
+ -- Constants --------------------------------------------------------------
14
15
 
15
16
  local IS_SERVER = RunService:IsServer()
16
17
  local DEFAULT_TIMEOUT = 5
17
18
 
18
- -- State ---------------------------------------------------------------
19
+ -- State ------------------------------------------------------------------
19
20
 
20
21
  local _nextCorrId = 0
21
22
  local _pending: { [number]: thread } = {}
23
+ local _pendingGather: { [number]: (any) -> () } = {}
22
24
 
23
- -- Public --------------------------------------------------------------
25
+ -- Private ----------------------------------------------------------------
24
26
 
25
- local Query = {}
27
+ local isPlayer = Util.isPlayer
28
+
29
+ local function nextCorr(): number
30
+ _nextCorrId += 1
31
+ return _nextCorrId
32
+ end
26
33
 
27
- export type QueryHandle = {
28
- handle: (self: QueryHandle, fn: (request: any, player: Player?) -> any) -> Types.Connection,
29
- request: (self: QueryHandle, data: any, target: any?) -> any,
30
- name: (self: QueryHandle) -> string,
31
- stats: (self: QueryHandle) -> Types.PacketStats,
34
+ -- Public types -----------------------------------------------------------
35
+
36
+ --[[
37
+ Server `request(data, target)` returns `Resp?` for a Player target and
38
+ `{ [Player]: Resp? }` for a multi-target table; Luau doesn't support
39
+ overloads on table fields, so the type advertises `Resp?` (the common
40
+ single-target case). Multi-target callers must cast the return.
41
+ ]]
42
+ export type QueryHandle<Req, Resp> = {
43
+ handle: (
44
+ self: QueryHandle<Req, Resp>,
45
+ fn: (request: Req, player: Player?) -> Resp?
46
+ ) -> Types.Connection,
47
+ request: (self: QueryHandle<Req, Resp>, data: Req, target: any?) -> Resp?,
48
+ name: (self: QueryHandle<Req, Resp>) -> string,
49
+ stats: (self: QueryHandle<Req, Resp>) -> Types.PacketStats,
32
50
  }
33
51
 
34
- function Query.define(
52
+ -- Public -----------------------------------------------------------------
53
+
54
+ local Query = {}
55
+
56
+ function Query.define<Req, Resp>(
35
57
  name: string,
36
- requestCodec: Types.InternalCodec<any>,
37
- responseCodec: Types.InternalCodec<any>,
58
+ requestCodec: Types.InternalCodec<Req>,
59
+ responseCodec: Types.InternalCodec<Resp>,
38
60
  options: Types.QueryOptions?
39
- ): QueryHandle
40
- local opts = options or {} :: Types.QueryOptions
61
+ ): QueryHandle<Req, Resp>
62
+ local opts = options or ({} :: Types.QueryOptions)
41
63
  local timeout = opts.timeout or DEFAULT_TIMEOUT
42
64
 
43
- -- Register request channel
44
65
  local reqOpenFn = Channel.resolveOpenFn(Channel.TS_NONE, name)
45
- local reqReg = Registry.register(
46
- name,
47
- Registry.KIND_REQUEST,
48
- requestCodec,
49
- false,
50
- nil,
51
- IS_SERVER,
52
- 1,
53
- Channel.TS_NONE,
54
- reqOpenFn,
55
- opts.rateLimit or { maxPerSecond = 30 },
56
- opts.validate,
57
- nil
58
- )
59
-
60
- -- Register response channel (paired)
61
- local respOpenFn = Channel.resolveOpenFn(Channel.TS_NONE, `{name}_resp`)
62
- local respReg = Registry.register(
63
- `{name}_resp`,
64
- Registry.KIND_RESPONSE,
65
- responseCodec,
66
- false,
67
- reqReg.id,
68
- false,
69
- 1,
70
- Channel.TS_NONE,
71
- respOpenFn,
72
- nil,
73
- nil,
74
- nil
75
- )
66
+ local respName = `{name}\0resp`
67
+ local respOpenFn = Channel.resolveOpenFn(Channel.TS_NONE, respName)
68
+
69
+ local reqReg = Registry.register({
70
+ name = name,
71
+ kind = Registry.KIND_REQUEST,
72
+ codec = requestCodec,
73
+ isUnreliable = false,
74
+ partner = nil,
75
+ needsGate = IS_SERVER,
76
+ headerSize = 1,
77
+ timestampMode = Channel.TS_NONE,
78
+ openFn = reqOpenFn,
79
+ rateLimit = opts.rateLimit or { maxPerSecond = 30 },
80
+ validate = opts.validate,
81
+ maxPayloadBytes = nil,
82
+ })
83
+
84
+ local respReg = Registry.register({
85
+ name = respName,
86
+ kind = Registry.KIND_RESPONSE,
87
+ codec = responseCodec,
88
+ isUnreliable = false,
89
+ partner = reqReg.id,
90
+ needsGate = false,
91
+ headerSize = 1,
92
+ timestampMode = Channel.TS_NONE,
93
+ openFn = respOpenFn,
94
+ rateLimit = nil,
95
+ validate = nil,
96
+ maxPayloadBytes = nil,
97
+ })
76
98
 
77
99
  reqReg.partner = respReg.id
78
100
 
79
- -- Lazy-require to avoid circular deps
80
- local _server: any = nil
81
- local _client: any = nil
82
-
83
- local function getServer(): any
84
- if not _server then
85
- _server = require(script.Parent.Parent.transport.Server)
86
- end
87
- return _server
88
- end
101
+ local activeHandler: ((any, Player?) -> any)? = nil
102
+ local handlerToken = 0
89
103
 
90
- local function getClient(): any
91
- if not _client then
92
- _client = require(script.Parent.Parent.transport.Client)
93
- end
94
- return _client
95
- end
96
-
97
- -- Listen for incoming responses and resolve pending requests
98
- respReg.signal:connect(function(value: any, sender: Player?, corrId: number)
99
- local waiting = _pending[corrId]
100
- if waiting then
104
+ respReg.signal:connect(function(value: any, _sender: Player?, corrId: number)
105
+ local single = _pending[corrId]
106
+ if single then
101
107
  _pending[corrId] = nil
102
- task.spawn(waiting, value)
108
+ task.spawn(single, value)
109
+ return
110
+ end
111
+ local gather = _pendingGather[corrId]
112
+ if gather then
113
+ _pendingGather[corrId] = nil
114
+ gather(value)
103
115
  end
104
116
  end)
105
117
 
106
- -- Listen for incoming requests and auto-respond
107
- local _handler: ((any, Player?) -> any)? = nil
108
-
109
118
  reqReg.signal:connect(function(value: any, sender: Player?, corrId: number)
110
- if not _handler then
119
+ local handler = activeHandler
120
+ if not handler then
111
121
  return
112
122
  end
113
123
 
114
- -- Run handler, get response
115
- local ok, response = pcall(_handler :: any, value, sender)
116
-
117
- if IS_SERVER then
118
- if sender then
119
- local Server = getServer()
120
- local ch = Server.getChannel(sender, false)
121
- if ok then
122
- Channel.writeQuery(ch, respReg.id, corrId, responseCodec, response)
123
- else
124
- Channel.writeQuery(ch, respReg.id, corrId, nil, nil)
125
- end
126
- end
127
- else
128
- local Client = getClient()
129
- local ch = Client.getChannel(false)
130
- if ok then
131
- Channel.writeQuery(ch, respReg.id, corrId, responseCodec, response)
132
- else
133
- Channel.writeQuery(ch, respReg.id, corrId, nil, nil)
134
- end
124
+ local ok, response = pcall(handler, value, sender)
125
+ local payloadCodec = if ok then responseCodec else nil
126
+ local payloadData = if ok then response else nil
127
+
128
+ local ch = if IS_SERVER and sender
129
+ then Transport.server().getChannel(sender, false)
130
+ elseif not IS_SERVER then Transport.client().getChannel(false)
131
+ else nil
132
+ if ch then
133
+ Channel.writeQuery(ch, respReg.id, corrId, payloadCodec, payloadData)
135
134
  end
136
135
  end)
137
136
 
138
- local handle: any = {}
139
-
140
- function handle.handle(
141
- _self: QueryHandle,
142
- fn: (request: any, player: Player?) -> any
143
- ): Types.Connection
144
- _handler = fn
145
-
146
- -- Return a connection that clears the handler on disconnect
147
- return table.freeze({
148
- connected = true,
149
- disconnect = function(self: Types.Connection): ()
150
- if not (self :: any).connected then
151
- return
152
- end
153
- (self :: any).connected = false
154
- _handler = nil
155
- end,
156
- } :: any)
157
- end
158
-
159
- function handle.request(_self: QueryHandle, data: any, target: any?): any
160
- _nextCorrId += 1
161
- local corrId = _nextCorrId
162
-
163
- if IS_SERVER then
164
- if target == nil then
165
- error(`[Lync] Query "{name}": server request requires a target`)
137
+ local function scheduleSingleTimeout(corrId: number, running: thread): ()
138
+ task.delay(timeout, function()
139
+ if _pending[corrId] == running then
140
+ _pending[corrId] = nil
141
+ task.spawn(running, nil)
166
142
  end
143
+ end)
144
+ end
167
145
 
168
- local Server = getServer()
146
+ local function requestSingle(target: Player, data: any): any
147
+ local corrId = nextCorr()
148
+ local ch = Transport.server().getChannel(target, false)
149
+ Channel.writeQuery(ch, reqReg.id, corrId, requestCodec, data)
169
150
 
170
- -- Single player
171
- if typeof(target) == "Instance" and target:IsA("Player") then
172
- local ch = Server.getChannel(target :: Player, false)
173
- Channel.writeQuery(ch, reqReg.id, corrId, requestCodec, data)
151
+ local running = coroutine.running()
152
+ _pending[corrId] = running
153
+ scheduleSingleTimeout(corrId, running)
154
+ return coroutine.yield()
155
+ end
174
156
 
175
- -- Wait for response
176
- local running = coroutine.running()
177
- _pending[corrId] = running
157
+ local function requestMulti(targets: { Player }, data: any): { [Player]: any }
158
+ if #targets == 0 then
159
+ return {}
160
+ end
178
161
 
179
- task.delay(timeout, function()
180
- if _pending[corrId] == running then
181
- _pending[corrId] = nil
182
- task.spawn(running, nil)
183
- end
184
- end)
162
+ local results: { [Player]: any } = {}
163
+ local remaining = #targets
164
+ local running = coroutine.running()
165
+ local resolved = false
185
166
 
186
- return coroutine.yield()
167
+ local function resolveOnce(): ()
168
+ if resolved then
169
+ return
187
170
  end
171
+ resolved = true
172
+ task.spawn(running, results)
173
+ end
188
174
 
189
- -- Multi-target: collect responses into { [Player]: response? }
190
- local targets: { Player } = {}
191
-
192
- if typeof(target) == "table" then
193
- local tbl = target :: any
194
- if tbl._lyncAll then
195
- targets = Players:GetPlayers()
196
- elseif tbl._lyncGroup then
197
- for player in tbl._members do
198
- table.insert(targets, player)
199
- end
200
- else
201
- for _, p in tbl do
202
- if typeof(p) == "Instance" and (p :: Instance):IsA("Player") then
203
- table.insert(targets, p :: Player)
204
- end
205
- end
175
+ local Server = Transport.server()
176
+ for _, player in targets do
177
+ local pCorr = nextCorr()
178
+ local ch = Server.getChannel(player, false)
179
+ Channel.writeQuery(ch, reqReg.id, pCorr, requestCodec, data)
180
+
181
+ local pRef = player
182
+ _pendingGather[pCorr] = function(response: any)
183
+ results[pRef] = response
184
+ remaining -= 1
185
+ if remaining == 0 then
186
+ resolveOnce()
206
187
  end
207
188
  end
189
+ end
208
190
 
209
- if #targets == 0 then
210
- return {}
211
- end
212
-
213
- local results: { [Player]: any } = {}
214
- local remaining = #targets
215
- local running = coroutine.running()
216
-
217
- for _, player in targets do
218
- _nextCorrId += 1
219
- local playerCorrId = _nextCorrId
220
- local ch = Server.getChannel(player, false)
221
- Channel.writeQuery(ch, reqReg.id, playerCorrId, requestCodec, data)
222
-
223
- local playerRef = player
224
- _pending[playerCorrId] = coroutine.create(function(response: any)
225
- results[playerRef] = response
226
- remaining -= 1
227
- if remaining == 0 then
228
- task.spawn(running, results)
229
- end
230
- end)
231
- end
191
+ task.delay(timeout, resolveOnce)
192
+ return coroutine.yield()
193
+ end
232
194
 
233
- -- Timeout
234
- task.delay(timeout, function()
235
- if remaining > 0 then
236
- remaining = 0
237
- task.spawn(running, results)
238
- end
239
- end)
195
+ local function collectTargets(target: any): { Player }
196
+ local targets: { Player } = {}
197
+ if typeof(target) ~= "table" then
198
+ return targets
199
+ end
240
200
 
241
- return coroutine.yield()
201
+ if target._lyncAll then
202
+ for _, p in Players:GetPlayers() do
203
+ table.insert(targets, p)
204
+ end
205
+ elseif target._lyncGroup then
206
+ for player in target._members do
207
+ table.insert(targets, player)
208
+ end
242
209
  else
243
- -- Client server
244
- local Client = getClient()
245
- local ch = Client.getChannel(false)
246
- Channel.writeQuery(ch, reqReg.id, corrId, requestCodec, data)
247
-
248
- local running = coroutine.running()
249
- _pending[corrId] = running
250
-
251
- task.delay(timeout, function()
252
- if _pending[corrId] == running then
253
- _pending[corrId] = nil
254
- task.spawn(running, nil)
210
+ for _, p in target do
211
+ if isPlayer(p) then
212
+ table.insert(targets, p :: Player)
255
213
  end
256
- end)
257
-
258
- return coroutine.yield()
214
+ end
259
215
  end
216
+ return targets
260
217
  end
261
218
 
262
- function handle.name(_self: QueryHandle): string
263
- return name
219
+ local function requestFromServer(data: any, target: any): any
220
+ if target == nil then
221
+ error(`[Lync] Query.request: "{name}" server request requires a target`)
222
+ end
223
+ if isPlayer(target) then
224
+ return requestSingle(target :: Player, data)
225
+ end
226
+ return requestMulti(collectTargets(target), data)
264
227
  end
265
228
 
266
- function handle.stats(_self: QueryHandle): Types.PacketStats
267
- return {
268
- bytesSent = reqReg.bytesSent + respReg.bytesSent,
269
- bytesReceived = reqReg.bytesReceived + respReg.bytesReceived,
270
- fires = reqReg.fires + respReg.fires,
271
- recvFires = reqReg.recvFires + respReg.recvFires,
272
- drops = reqReg.drops + respReg.drops,
273
- }
229
+ local function requestFromClient(data: any): any
230
+ local corrId = nextCorr()
231
+ local ch = Transport.client().getChannel(false)
232
+ Channel.writeQuery(ch, reqReg.id, corrId, requestCodec, data)
233
+
234
+ local running = coroutine.running()
235
+ _pending[corrId] = running
236
+ scheduleSingleTimeout(corrId, running)
237
+ return coroutine.yield()
274
238
  end
275
239
 
276
- return table.freeze(handle)
240
+ type Self = QueryHandle<Req, Resp>
241
+
242
+ local handle = {
243
+ handle = function(
244
+ _self: Self,
245
+ fn: (request: Req, player: Player?) -> Resp?
246
+ ): Types.Connection
247
+ handlerToken += 1
248
+ local myToken = handlerToken
249
+ activeHandler = fn :: any
250
+
251
+ local conn = { connected = true }
252
+ function conn.disconnect(self): ()
253
+ if not self.connected then
254
+ return
255
+ end
256
+ self.connected = false
257
+ if handlerToken == myToken then
258
+ activeHandler = nil
259
+ end
260
+ end
261
+ return conn :: Types.Connection
262
+ end,
263
+
264
+ request = function(_self: Self, data: Req, target: any?): Resp?
265
+ if IS_SERVER then
266
+ return requestFromServer(data, target)
267
+ end
268
+ return requestFromClient(data)
269
+ end,
270
+
271
+ name = function(_self: Self): string
272
+ return name
273
+ end,
274
+
275
+ stats = function(_self: Self): Types.PacketStats
276
+ return {
277
+ bytesSent = reqReg.bytesSent + respReg.bytesSent,
278
+ bytesReceived = reqReg.bytesReceived + respReg.bytesReceived,
279
+ fires = reqReg.fires + respReg.fires,
280
+ recvFires = reqReg.recvFires + respReg.recvFires,
281
+ drops = reqReg.drops + respReg.drops,
282
+ }
283
+ end,
284
+ }
285
+
286
+ return table.freeze(handle) :: QueryHandle<Req, Resp>
277
287
  end
278
288
 
279
289
  function Query.pendingCount(): number
@@ -281,6 +291,9 @@ function Query.pendingCount(): number
281
291
  for _ in _pending do
282
292
  count += 1
283
293
  end
294
+ for _ in _pendingGather do
295
+ count += 1
296
+ end
284
297
  return count
285
298
  end
286
299
 
@@ -1,76 +1,74 @@
1
1
  --!strict
2
2
  --!optimize 2
3
- -- Batched connection lifecycle management.
3
+ -- Batched connection lifecycle. Accepts Lync Connection,
4
+ -- RBXScriptConnection, and any object with :disconnect() / :Disconnect().
4
5
 
5
6
  local Types = require(script.Parent.Parent.Types)
6
7
 
7
- -- Public --------------------------------------------------------------
8
-
9
- local Scope = {}
8
+ -- Public types -----------------------------------------------------------
10
9
 
11
10
  export type ScopeHandle = {
12
11
  on: (self: ScopeHandle, source: any, fn: (...any) -> ()) -> Types.Connection,
13
12
  once: (self: ScopeHandle, source: any, fn: (...any) -> ()) -> Types.Connection,
14
13
  add: (self: ScopeHandle, connection: any) -> (),
14
+ size: (self: ScopeHandle) -> number,
15
15
  destroy: (self: ScopeHandle) -> (),
16
16
  }
17
17
 
18
+ type Scope = ScopeHandle & { _connections: { any }, _count: number }
19
+
20
+ -- Private ----------------------------------------------------------------
21
+
22
+ local function pushConnection(self: Scope, conn: any): ()
23
+ local idx = self._count + 1
24
+ self._count = idx
25
+ self._connections[idx] = conn
26
+ end
27
+
28
+ -- Public -----------------------------------------------------------------
29
+
18
30
  local ScopeMeta = {}
19
31
  ScopeMeta.__index = ScopeMeta
20
32
 
21
- function ScopeMeta.on(self: any, source: any, fn: (...any) -> ()): Types.Connection
33
+ function ScopeMeta.on(self: Scope, source: any, fn: (...any) -> ()): Types.Connection
22
34
  local conn = source:on(fn)
23
- local conns = self._connections
24
- local idx = self._count + 1
25
- self._count = idx
26
- conns[idx] = conn
35
+ pushConnection(self, conn)
27
36
  return conn
28
37
  end
29
38
 
30
- function ScopeMeta.once(self: any, source: any, fn: (...any) -> ()): Types.Connection
39
+ function ScopeMeta.once(self: Scope, source: any, fn: (...any) -> ()): Types.Connection
31
40
  local conn = source:once(fn)
32
- local conns = self._connections
33
- local idx = self._count + 1
34
- self._count = idx
35
- conns[idx] = conn
41
+ pushConnection(self, conn)
36
42
  return conn
37
43
  end
38
44
 
39
- function ScopeMeta.add(self: any, connection: any): ()
40
- local idx = self._count + 1
41
- self._count = idx
42
- self._connections[idx] = connection
45
+ ScopeMeta.add = pushConnection
46
+
47
+ function ScopeMeta.size(self: Scope): number
48
+ return self._count
43
49
  end
44
50
 
45
- function ScopeMeta.destroy(self: any): ()
51
+ function ScopeMeta.destroy(self: Scope): ()
46
52
  local conns = self._connections
47
53
  local count = self._count
48
-
49
54
  for i = 1, count do
50
55
  local conn = conns[i]
51
- conns[i] = nil :: any
52
-
53
- --[[
54
- Support both Lync Connection (conn:disconnect())
55
- and RBXScriptConnection (conn:Disconnect()).
56
- ]]
57
- if typeof(conn) == "table" then
58
- if conn.disconnect then
59
- conn:disconnect()
60
- end
61
- elseif typeof(conn) == "RBXScriptConnection" then
56
+ conns[i] = nil
57
+ if typeof(conn) == "RBXScriptConnection" then
62
58
  (conn :: RBXScriptConnection):Disconnect()
59
+ elseif typeof(conn) == "table" and conn.disconnect then
60
+ conn:disconnect()
63
61
  end
64
62
  end
65
-
66
63
  self._count = 0
67
64
  end
68
65
 
69
- function Scope.create(): ScopeHandle
70
- return setmetatable({
71
- _connections = {},
72
- _count = 0,
73
- }, ScopeMeta) :: any
66
+ table.freeze(ScopeMeta)
67
+
68
+ local ScopeModule = {}
69
+
70
+ function ScopeModule.create(): ScopeHandle
71
+ return (setmetatable({ _connections = {}, _count = 0 }, ScopeMeta) :: any) :: ScopeHandle
74
72
  end
75
73
 
76
- return table.freeze(Scope)
74
+ return table.freeze(ScopeModule)