@axpecter/lync 2.0.0 → 2.1.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 (55) hide show
  1. package/README.md +342 -428
  2. package/package.json +1 -1
  3. package/src/Types.luau +63 -31
  4. package/src/api/Group.luau +101 -106
  5. package/src/api/Packet.luau +187 -186
  6. package/src/api/Query.luau +223 -284
  7. package/src/api/Scope.luau +46 -57
  8. package/src/api/Signal.luau +104 -137
  9. package/src/codec/Base.luau +69 -20
  10. package/src/codec/composite/Array.luau +179 -270
  11. package/src/codec/composite/Map.luau +97 -347
  12. package/src/codec/composite/Optional.luau +27 -24
  13. package/src/codec/composite/Shared.luau +96 -65
  14. package/src/codec/composite/Struct.luau +200 -360
  15. package/src/codec/composite/Tagged.luau +62 -187
  16. package/src/codec/composite/Tuple.luau +79 -103
  17. package/src/codec/datatype/Buffer.luau +49 -21
  18. package/src/codec/datatype/CFrame.luau +207 -30
  19. package/src/codec/datatype/Color.luau +21 -11
  20. package/src/codec/datatype/Instance.luau +28 -21
  21. package/src/codec/datatype/IntVector.luau +33 -21
  22. package/src/codec/datatype/NumberRange.luau +19 -10
  23. package/src/codec/datatype/Ray.luau +26 -22
  24. package/src/codec/datatype/Rect.luau +20 -16
  25. package/src/codec/datatype/Region.luau +51 -45
  26. package/src/codec/datatype/Sequence.luau +64 -56
  27. package/src/codec/datatype/String.luau +89 -56
  28. package/src/codec/datatype/UDim.luau +40 -22
  29. package/src/codec/datatype/Vector.luau +181 -17
  30. package/src/codec/meta/Auto.luau +295 -253
  31. package/src/codec/meta/Bitfield.luau +104 -148
  32. package/src/codec/meta/Custom.luau +8 -4
  33. package/src/codec/meta/Enum.luau +29 -57
  34. package/src/codec/meta/Float.luau +64 -0
  35. package/src/codec/meta/Nothing.luau +9 -5
  36. package/src/codec/meta/Unknown.luau +44 -36
  37. package/src/codec/primitive/Bool.luau +16 -26
  38. package/src/codec/primitive/Float16.luau +58 -64
  39. package/src/codec/primitive/Int.luau +68 -0
  40. package/src/codec/primitive/Number.luau +21 -43
  41. package/src/codec/primitive/Varint.luau +67 -46
  42. package/src/index.d.ts +251 -335
  43. package/src/init.luau +319 -207
  44. package/src/internal/Baseline.luau +29 -24
  45. package/src/internal/Channel.luau +333 -278
  46. package/src/internal/Middleware.luau +60 -85
  47. package/src/internal/Pool.luau +19 -30
  48. package/src/internal/Registry.luau +56 -113
  49. package/src/transport/Bridge.luau +64 -43
  50. package/src/transport/Client.luau +59 -170
  51. package/src/transport/Gate.luau +282 -142
  52. package/src/transport/Reader.luau +148 -140
  53. package/src/transport/Server.luau +138 -488
  54. package/src/api/Namespace.luau +0 -256
  55. package/src/codec/meta/Quantized.luau +0 -174
@@ -2,414 +2,164 @@
2
2
  --!native
3
3
  -- map and deltaMap codecs.
4
4
 
5
- local Baseline = require(script.Parent.Parent.Parent.internal.Baseline)
6
- local Channel = require(script.Parent.Parent.Parent.internal.Channel)
5
+ local Base = require(script.Parent.Parent.Base)
7
6
  local Shared = require(script.Parent.Shared)
8
7
  local Types = require(script.Parent.Parent.Parent.Types)
9
8
  local Varint = require(script.Parent.Parent.primitive.Varint)
10
9
 
11
- type ChannelState = Types.ChannelState
12
- type Codec<T> = Types.Codec<T>
13
- type InternalCodec<T> = Types.InternalCodec<T>
10
+ -- Private -------------------------------------------------------------
11
+
12
+ local alloc = Base.alloc
13
+ local writeu8 = buffer.writeu8
14
+ local readu8 = buffer.readu8
14
15
 
15
- local alloc = Channel.alloc
16
- local rangeEqual = Shared.rangeEqual
17
16
  local acquireScratch = Shared.acquireScratch
18
17
  local releaseScratch = Shared.releaseScratch
19
-
20
- local FLAG_DELTA = Shared.FLAG_DELTA
21
- local FLAG_FULL = Shared.FLAG_FULL
22
- local FLAG_UNCHANGED = Shared.FLAG_UNCHANGED
18
+ local rangeEqual = Shared.rangeEqual
19
+ local allocDeltaId = Shared.allocDeltaId
20
+ local DELTA_UNCHANGED = Shared.DELTA_UNCHANGED
23
21
 
24
22
  -- Public --------------------------------------------------------------
25
23
 
26
24
  local Map = {}
27
25
 
28
26
  function Map.map(
29
- keyCodec: Codec<any>,
30
- valueCodec: Codec<any>,
27
+ keyCodec: Types.InternalCodec<any>,
28
+ valueCodec: Types.InternalCodec<any>,
31
29
  maxCount: number?
32
- ): Codec<{ [any]: any }>
33
- local keyInternal = keyCodec :: InternalCodec<any>
34
- local valInternal = valueCodec :: InternalCodec<any>
35
-
36
- local keySize = keyInternal._size
37
- local valSize = valInternal._size
38
- local keyDWrite = keyInternal._directWrite
39
- local valDWrite = valInternal._directWrite
40
- local keyDRead = keyInternal._directRead
41
- local valDRead = valInternal._directRead
42
-
43
- if keySize and valSize and keyDWrite and valDWrite and keyDRead and valDRead then
44
- local entrySize = keySize + valSize
45
-
46
- return table.freeze({
47
- _hasUnknown = (keyCodec :: any)._hasUnknown or (valueCodec :: any)._hasUnknown or nil,
48
- write = function(ch: ChannelState, value: { [any]: any }): ()
49
- local count = 0
50
- for _ in value do
51
- count += 1
52
- end
53
-
54
- Varint.write(ch, count)
55
-
56
- if count > 0 then
57
- local totalBytes = count * entrySize
58
- local c = ch.cursor
59
- if c + totalBytes > ch.size then
60
- alloc(ch, totalBytes)
61
- end
62
-
63
- local b = ch.buff
64
- for k, v in value do
65
- keyDWrite(b, c, k)
66
- valDWrite(b, c + keySize, v)
67
- c += entrySize
68
- end
69
- ch.cursor = c
70
- end
71
- end,
72
- read = function(
73
- src: buffer,
74
- pos: number,
75
- _refs: { Instance }?
76
- ): ({ [any]: any }, number)
77
- local count, lenBytes = Varint.read(src, pos)
78
-
79
- if maxCount and count > maxCount then
80
- error(`[Lync] Map count {count} exceeds max {maxCount}`)
81
- end
82
-
83
- local result = {}
84
- local c = pos + lenBytes
85
-
86
- for _ = 1, count do
87
- local k = keyDRead(src, c)
88
- local v = valDRead(src, c + keySize)
89
- result[k] = v
90
- c += entrySize
91
- end
92
-
93
- return result, c - pos
94
- end,
95
- })
96
- end
97
-
98
- local writeKey = keyCodec.write
99
- local writeVal = valueCodec.write
100
- local readKey = keyCodec.read
101
- local readVal = valueCodec.read
102
-
30
+ ): Types.InternalCodec<any>
103
31
  return table.freeze({
104
- _hasUnknown = (keyCodec :: any)._hasUnknown or (valueCodec :: any)._hasUnknown or nil,
105
- write = function(ch: ChannelState, value: { [any]: any }): ()
32
+ write = function(ch: Types.ChannelState, value: any): ()
33
+ -- Count entries
106
34
  local count = 0
107
35
  for _ in value do
108
36
  count += 1
109
37
  end
110
38
 
111
39
  Varint.write(ch, count)
40
+ if count == 0 then
41
+ return
42
+ end
43
+
44
+ -- Pre-alloc if both fixed size
45
+ local kAny = keyCodec :: any
46
+ local vAny = valueCodec :: any
47
+ if kAny._size and vAny._size then
48
+ local entrySize = kAny._size + vAny._size
49
+ local cursor = ch.cursor
50
+ if cursor + count * entrySize > ch.size then
51
+ alloc(ch, count * entrySize)
52
+ end
53
+ end
112
54
 
113
55
  for k, v in value do
114
- writeKey(ch, k)
115
- writeVal(ch, v)
56
+ keyCodec.write(ch, k)
57
+ valueCodec.write(ch, v)
116
58
  end
117
59
  end,
118
- read = function(src: buffer, pos: number, refs: { Instance }?): ({ [any]: any }, number)
119
- local count, lenBytes = Varint.read(src, pos)
120
60
 
61
+ read = function(src: buffer, pos: number, refs: { Instance }?): (any, number)
62
+ local count, lenBytes = Varint.read(src, pos)
121
63
  if maxCount and count > maxCount then
122
- error(`[Lync] Map count {count} exceeds max {maxCount}`)
64
+ error(`[Lync] Map: count {count} exceeds max {maxCount}`)
123
65
  end
124
66
 
125
- local result = {}
126
- local absPos = pos + lenBytes
67
+ local result: { [any]: any } = {}
68
+ local total = lenBytes
127
69
 
128
70
  for _ = 1, count do
129
- local k, kn = readKey(src, absPos, refs)
130
- absPos += kn
131
- local v, vn = readVal(src, absPos, refs)
132
- absPos += vn
71
+ local k, kc = keyCodec.read(src, pos + total, refs)
72
+ total += kc
73
+ local v, vc = valueCodec.read(src, pos + total, refs)
74
+ total += vc
133
75
  result[k] = v
134
76
  end
135
77
 
136
- return result, absPos - pos
78
+ return result, total
137
79
  end,
138
- })
80
+ } :: any)
139
81
  end
140
82
 
141
- --[[
142
- Delta map: tracks per-key changes. First frame is full, then upserts + removes only.
143
-
144
- Wire format:
145
- FLAG_FULL: u8(1) + varint(count) + [key+value ...]
146
- FLAG_UNCHANGED: u8(2)
147
- FLAG_DELTA: u8(0) + varint(upsertN) + [key+value ...] + varint(removeN) + [key ...]
148
- ]]
149
83
  function Map.deltaMap(
150
- keyCodec: Codec<any>,
151
- valueCodec: Codec<any>,
84
+ keyCodec: Types.InternalCodec<any>,
85
+ valueCodec: Types.InternalCodec<any>,
152
86
  maxCount: number?
153
- ): Codec<{ [any]: any }>
154
- local deltaId = Shared.allocDeltaId()
155
-
156
- local writeKey = keyCodec.write
157
- local writeVal = valueCodec.write
158
- local readKey = keyCodec.read
159
- local readVal = valueCodec.read
160
-
161
- local keyInternal = keyCodec :: InternalCodec<any>
162
- local valInternal = valueCodec :: InternalCodec<any>
163
-
164
- local keySize = keyInternal._size
165
- local valSize = valInternal._size
166
- local keyDWrite = keyInternal._directWrite
167
- local valDWrite = valInternal._directWrite
168
- local keyDRead = keyInternal._directRead
169
- local valDRead = valInternal._directRead
170
-
171
- local scratchDirect = keySize and valSize and keyDWrite and valDWrite
172
- local entrySize = if keySize and valSize then keySize + valSize else nil
173
-
174
- type EntryBounds = {
175
- keyOff: number,
176
- keyLen: number,
177
- valOff: number,
178
- valLen: number,
179
- }
180
-
181
- type DeltaMapCache = {
182
- raw: buffer,
183
- entries: { EntryBounds },
184
- keyLookup: { [string]: number },
185
- count: number,
186
- }
87
+ ): Types.InternalCodec<any>
88
+ local deltaId = allocDeltaId()
89
+ local innerMap = Map.map(keyCodec, valueCodec, maxCount)
187
90
 
188
91
  return table.freeze({
189
92
  _isDelta = true,
190
- _hasUnknown = (keyCodec :: any)._hasUnknown or (valueCodec :: any)._hasUnknown or nil,
191
-
192
- write = function(ch: ChannelState, value: { [any]: any }): ()
193
- local scratch = acquireScratch()
194
-
195
- local entries = {} :: { EntryBounds }
196
- local keyLookup = {} :: { [string]: number }
197
- local entryCount = 0
198
-
199
- if scratchDirect then
200
- for k, v in value do
201
- entryCount += 1
202
- local c = scratch.cursor
203
- alloc(scratch, entrySize :: number)
204
- local b = scratch.buff
205
- keyDWrite(b, c, k)
206
- valDWrite(b, c + (keySize :: number), v)
207
- scratch.cursor = c + (entrySize :: number)
208
-
209
- entries[entryCount] = {
210
- keyOff = c,
211
- keyLen = keySize :: number,
212
- valOff = c + (keySize :: number),
213
- valLen = valSize :: number,
214
- }
215
-
216
- local keyId = buffer.readstring(b, c, keySize :: number)
217
- keyLookup[keyId] = entryCount
218
- end
219
- else
220
- for k, v in value do
221
- entryCount += 1
222
- local keyStart = scratch.cursor
223
- writeKey(scratch, k)
224
- local keyEnd = scratch.cursor
225
- writeVal(scratch, v)
226
- local valEnd = scratch.cursor
227
93
 
228
- entries[entryCount] = {
229
- keyOff = keyStart,
230
- keyLen = keyEnd - keyStart,
231
- valOff = keyEnd,
232
- valLen = valEnd - keyEnd,
233
- }
234
-
235
- local keyId = buffer.readstring(scratch.buff, keyStart, keyEnd - keyStart)
236
- keyLookup[keyId] = entryCount
237
- end
238
- end
239
-
240
- local scratchTotal = scratch.cursor
241
- local cache = ch.deltas[deltaId] :: DeltaMapCache?
94
+ write = function(ch: Types.ChannelState, value: any): ()
95
+ local cache = ch.deltas[deltaId]
242
96
 
243
97
  if not cache then
244
- alloc(ch, 1)
245
- buffer.writeu8(ch.buff, ch.cursor, FLAG_FULL)
246
- ch.cursor += 1
247
- Varint.write(ch, entryCount)
248
-
249
- if scratchTotal > 0 then
250
- alloc(ch, scratchTotal)
251
- buffer.copy(ch.buff, ch.cursor, scratch.buff, 0, scratchTotal)
252
- ch.cursor += scratchTotal
253
- end
254
- else
255
- local cacheRaw = cache.raw
256
- local cacheEntries = cache.entries
257
- local cacheLookup = cache.keyLookup
258
-
259
- local upsertKeys = {} :: { number }
260
- local upsertN = 0
261
-
262
- for keyId, idx in keyLookup do
263
- local oldIdx = cacheLookup[keyId]
264
- if not oldIdx then
265
- upsertN += 1
266
- upsertKeys[upsertN] = idx
267
- else
268
- local newE = entries[idx]
269
- local oldE = cacheEntries[oldIdx]
270
- if
271
- newE.valLen ~= oldE.valLen
272
- or not rangeEqual(
273
- scratch.buff,
274
- newE.valOff,
275
- cacheRaw,
276
- oldE.valOff,
277
- newE.valLen
278
- )
279
- then
280
- upsertN += 1
281
- upsertKeys[upsertN] = idx
282
- end
283
- end
284
- end
285
-
286
- local removeKeyIds = {} :: { string }
287
- local removeN = 0
288
-
289
- for keyId in cacheLookup do
290
- if not keyLookup[keyId] then
291
- removeN += 1
292
- removeKeyIds[removeN] = keyId
293
- end
294
- end
295
-
296
- if upsertN == 0 and removeN == 0 then
98
+ local cursor = ch.cursor
99
+ if cursor + 1 > ch.size then
297
100
  alloc(ch, 1)
298
- buffer.writeu8(ch.buff, ch.cursor, FLAG_UNCHANGED)
299
- ch.cursor += 1
300
- releaseScratch()
301
- return
302
- end
303
-
304
- alloc(ch, 1)
305
- buffer.writeu8(ch.buff, ch.cursor, FLAG_DELTA)
306
- ch.cursor += 1
307
-
308
- Varint.write(ch, upsertN)
309
- for i = 1, upsertN do
310
- local e = entries[upsertKeys[i]]
311
- local totalLen = e.keyLen + e.valLen
312
- alloc(ch, totalLen)
313
- buffer.copy(ch.buff, ch.cursor, scratch.buff, e.keyOff, totalLen)
314
- ch.cursor += totalLen
315
101
  end
102
+ writeu8(ch.buff, cursor, 3) -- FLAG_FULL
103
+ ch.cursor = cursor + 1
104
+
105
+ local scratch = acquireScratch()
106
+ innerMap.write(scratch, value)
107
+ local snapLen = scratch.cursor
108
+ local snapBuf = buffer.create(snapLen)
109
+ buffer.copy(snapBuf, 0, scratch.buff, 0, snapLen)
110
+
111
+ ch.deltas[deltaId] = { raw = snapBuf, len = snapLen }
112
+ innerMap.write(ch, value)
113
+ releaseScratch()
114
+ return
115
+ end
316
116
 
317
- Varint.write(ch, removeN)
318
- for i = 1, removeN do
319
- local keyId = removeKeyIds[i]
320
- local oldIdx = cacheLookup[keyId]
321
- local oldE = cacheEntries[oldIdx]
322
- alloc(ch, oldE.keyLen)
323
- buffer.copy(ch.buff, ch.cursor, cacheRaw, oldE.keyOff, oldE.keyLen)
324
- ch.cursor += oldE.keyLen
117
+ local scratch = acquireScratch()
118
+ innerMap.write(scratch, value)
119
+ local scratchLen = scratch.cursor
120
+ local cachedLen = cache.len
121
+
122
+ if
123
+ scratchLen == cachedLen and rangeEqual(scratch.buff, 0, cache.raw, 0, scratchLen)
124
+ then
125
+ local cursor = ch.cursor
126
+ if cursor + 1 > ch.size then
127
+ alloc(ch, 1)
325
128
  end
129
+ writeu8(ch.buff, cursor, DELTA_UNCHANGED)
130
+ ch.cursor = cursor + 1
131
+ releaseScratch()
132
+ return
326
133
  end
327
134
 
328
- local rawBuf = buffer.create(scratchTotal)
329
- if scratchTotal > 0 then
330
- buffer.copy(rawBuf, 0, scratch.buff, 0, scratchTotal)
135
+ -- Changed: full re-send
136
+ local cursor = ch.cursor
137
+ if cursor + 1 > ch.size then
138
+ alloc(ch, 1)
331
139
  end
140
+ writeu8(ch.buff, cursor, 3) -- FLAG_FULL
141
+ ch.cursor = cursor + 1
142
+ innerMap.write(ch, value)
332
143
 
333
- ch.deltas[deltaId] = {
334
- raw = rawBuf,
335
- entries = entries,
336
- keyLookup = keyLookup,
337
- count = entryCount,
338
- }
144
+ local newBuf = buffer.create(scratchLen)
145
+ buffer.copy(newBuf, 0, scratch.buff, 0, scratchLen)
146
+ cache.raw = newBuf
147
+ cache.len = scratchLen
339
148
 
340
149
  releaseScratch()
341
150
  end,
342
151
 
343
- read = function(src: buffer, pos: number, refs: { Instance }?): ({ [any]: any }, number)
344
- local flag = buffer.readu8(src, pos)
345
- local absPos = pos + 1
346
-
347
- if flag == FLAG_UNCHANGED then
348
- local cache = Baseline.getCache(deltaId)
349
- return if cache then table.clone(cache) else {}, 1
350
- end
351
-
352
- if flag == FLAG_FULL then
353
- local count, lenBytes = Varint.read(src, absPos)
354
- absPos += lenBytes
355
-
356
- if maxCount and count > maxCount then
357
- error(`[Lync] Map count {count} exceeds max {maxCount}`)
358
- end
359
-
360
- local result = {}
361
-
362
- if keyDRead and valDRead and keySize and valSize then
363
- for _ = 1, count do
364
- local k = keyDRead(src, absPos)
365
- local v = valDRead(src, absPos + keySize)
366
- result[k] = v
367
- absPos += entrySize :: number
368
- end
369
- else
370
- for _ = 1, count do
371
- local k, kn = readKey(src, absPos, refs)
372
- absPos += kn
373
- local v, vn = readVal(src, absPos, refs)
374
- absPos += vn
375
- result[k] = v
376
- end
377
- end
378
-
379
- Baseline.setCache(deltaId, result)
380
- return result, absPos - pos
381
- end
382
-
383
- local cache = Baseline.getCache(deltaId) :: { [any]: any }?
384
- if not cache then
385
- error("[Lync] Delta frame without baseline")
386
- end
387
- local result = table.clone(cache)
388
-
389
- local upsertN, unBytes = Varint.read(src, absPos)
390
- absPos += unBytes
391
-
392
- for _ = 1, upsertN do
393
- local k, kn = readKey(src, absPos, refs)
394
- absPos += kn
395
- local v, vn = readVal(src, absPos, refs)
396
- absPos += vn
397
- result[k] = v
398
- end
399
-
400
- local removeN, rmBytes = Varint.read(src, absPos)
401
- absPos += rmBytes
152
+ read = function(src: buffer, pos: number, refs: { Instance }?): (any, number)
153
+ local flag = readu8(src, pos)
402
154
 
403
- for _ = 1, removeN do
404
- local k, kn = readKey(src, absPos, refs)
405
- absPos += kn
406
- result[k] = nil
155
+ if flag == DELTA_UNCHANGED then
156
+ return {} :: any, 1
407
157
  end
408
158
 
409
- Baseline.setCache(deltaId, result)
410
- return result, absPos - pos
159
+ local value, consumed = innerMap.read(src, pos + 1, refs)
160
+ return value, 1 + consumed
411
161
  end,
412
- })
162
+ } :: any)
413
163
  end
414
164
 
415
165
  return table.freeze(Map)
@@ -1,49 +1,52 @@
1
1
  --!strict
2
- --!native
2
+ --!optimize 2
3
3
  -- Optional codec. 1 byte flag, value only if present.
4
4
 
5
- local Channel = require(script.Parent.Parent.Parent.internal.Channel)
5
+ local Base = require(script.Parent.Parent.Base)
6
6
  local Types = require(script.Parent.Parent.Parent.Types)
7
7
 
8
- type ChannelState = Types.ChannelState
9
- type Codec<T> = Types.Codec<T>
8
+ -- Private -------------------------------------------------------------
10
9
 
11
- local alloc = Channel.alloc
10
+ local alloc = Base.alloc
11
+ local writeu8 = buffer.writeu8
12
+ local readu8 = buffer.readu8
12
13
 
13
14
  -- Public --------------------------------------------------------------
14
15
 
15
16
  local Optional = {}
16
17
 
17
- function Optional.optional(inner: Codec<any>): Codec<any>
18
- local innerWrite = inner.write
19
- local innerRead = inner.read
20
-
21
- return table.freeze({
22
- _hasUnknown = (inner :: any)._hasUnknown or nil,
23
- write = function(ch: ChannelState, value: any): ()
24
- local c = ch.cursor
25
- if c + 1 > ch.size then
18
+ function Optional.optional(inner: Types.InternalCodec<any>): Types.InternalCodec<any>
19
+ local codec: any = {
20
+ write = function(ch: Types.ChannelState, value: any): ()
21
+ local cursor = ch.cursor
22
+ if cursor + 1 > ch.size then
26
23
  alloc(ch, 1)
27
24
  end
28
25
  if value == nil then
29
- buffer.writeu8(ch.buff, c, 0)
30
- ch.cursor = c + 1
26
+ writeu8(ch.buff, cursor, 0)
27
+ ch.cursor = cursor + 1
31
28
  else
32
- buffer.writeu8(ch.buff, c, 1)
33
- ch.cursor = c + 1
34
- innerWrite(ch, value)
29
+ writeu8(ch.buff, cursor, 1)
30
+ ch.cursor = cursor + 1
31
+ inner.write(ch, value)
35
32
  end
36
33
  end,
34
+
37
35
  read = function(src: buffer, pos: number, refs: { Instance }?): (any, number)
38
- local flag = buffer.readu8(src, pos)
36
+ local flag = readu8(src, pos)
39
37
  if flag == 0 then
40
38
  return nil, 1
41
39
  end
42
-
43
- local value, n = innerRead(src, pos + 1, refs)
44
- return value, 1 + n
40
+ local value, consumed = inner.read(src, pos + 1, refs)
41
+ return value, 1 + consumed
45
42
  end,
46
- })
43
+ }
44
+
45
+ if (inner :: any)._hasUnknown then
46
+ codec._hasUnknown = true
47
+ end
48
+
49
+ return table.freeze(codec)
47
50
  end
48
51
 
49
52
  return table.freeze(Optional)