@trioforge/crucible 0.1.0 → 0.2.0

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 (134) hide show
  1. package/out/client/index.d.ts +7 -0
  2. package/out/client/init.luau +36 -1
  3. package/out/configs.d.ts +37 -0
  4. package/out/configs.luau +46 -0
  5. package/out/data.d.ts +224 -0
  6. package/out/data.luau +97 -0
  7. package/out/emit/configs.d.ts +4 -0
  8. package/out/emit/configs.luau +22 -0
  9. package/out/emit/data.d.ts +6 -0
  10. package/out/emit/data.luau +19 -0
  11. package/out/emit/index.d.ts +5 -1
  12. package/out/emit/init.luau +23 -7
  13. package/out/emit/network.d.ts +6 -0
  14. package/out/emit/network.luau +36 -0
  15. package/out/emit/replicated.d.ts +4 -0
  16. package/out/emit/replicated.luau +13 -0
  17. package/out/index.d.ts +8 -0
  18. package/out/init.luau +23 -0
  19. package/out/internal/binding.luau +5 -0
  20. package/out/internal/boot.luau +107 -8
  21. package/out/internal/configs/limits.d.ts +10 -0
  22. package/out/internal/configs/limits.luau +25 -0
  23. package/out/internal/configs/port.d.ts +29 -0
  24. package/out/internal/configs/port.luau +99 -0
  25. package/out/internal/configs/runtime.d.ts +21 -0
  26. package/out/internal/configs/runtime.luau +981 -0
  27. package/out/internal/data/admission.d.ts +1 -0
  28. package/out/internal/data/admission.luau +673 -0
  29. package/out/internal/data/declarations.d.ts +1 -0
  30. package/out/internal/data/declarations.luau +384 -0
  31. package/out/internal/data/envelope.d.ts +1 -0
  32. package/out/internal/data/envelope.luau +318 -0
  33. package/out/internal/data/handles.d.ts +1 -0
  34. package/out/internal/data/handles.luau +255 -0
  35. package/out/internal/data/inbox-transforms.d.ts +1 -0
  36. package/out/internal/data/inbox-transforms.luau +458 -0
  37. package/out/internal/data/inbox.d.ts +1 -0
  38. package/out/internal/data/inbox.luau +437 -0
  39. package/out/internal/data/lease.d.ts +1 -0
  40. package/out/internal/data/lease.luau +304 -0
  41. package/out/internal/data/limits.d.ts +1 -0
  42. package/out/internal/data/limits.luau +234 -0
  43. package/out/internal/data/messages.d.ts +1 -0
  44. package/out/internal/data/messages.luau +69 -0
  45. package/out/internal/data/operation-id.d.ts +1 -0
  46. package/out/internal/data/operation-id.luau +61 -0
  47. package/out/internal/data/paths.d.ts +2 -0
  48. package/out/internal/data/paths.luau +323 -0
  49. package/out/internal/data/port.d.ts +1 -0
  50. package/out/internal/data/port.luau +290 -0
  51. package/out/internal/data/profiles.d.ts +1 -0
  52. package/out/internal/data/profiles.luau +862 -0
  53. package/out/internal/data/receipts.d.ts +1 -0
  54. package/out/internal/data/receipts.luau +190 -0
  55. package/out/internal/data/record-bindings.d.ts +1 -0
  56. package/out/internal/data/record-bindings.luau +92 -0
  57. package/out/internal/data/record-declarations.d.ts +1 -0
  58. package/out/internal/data/record-declarations.luau +140 -0
  59. package/out/internal/data/records.d.ts +1 -0
  60. package/out/internal/data/records.luau +400 -0
  61. package/out/internal/data/runtime.d.ts +1 -0
  62. package/out/internal/data/runtime.luau +918 -0
  63. package/out/internal/data/scheduler.d.ts +1 -0
  64. package/out/internal/data/scheduler.luau +507 -0
  65. package/out/internal/data/session.d.ts +1 -0
  66. package/out/internal/data/session.luau +234 -0
  67. package/out/internal/data/shutdown.d.ts +1 -0
  68. package/out/internal/data/shutdown.luau +91 -0
  69. package/out/internal/data/waiters.d.ts +1 -0
  70. package/out/internal/data/waiters.luau +65 -0
  71. package/out/internal/lifecycle.luau +45 -0
  72. package/out/internal/network/catalog.d.ts +26 -0
  73. package/out/internal/network/catalog.luau +182 -0
  74. package/out/internal/network/claims.d.ts +1 -0
  75. package/out/internal/network/claims.luau +154 -0
  76. package/out/internal/network/client-session.d.ts +1 -0
  77. package/out/internal/network/client-session.luau +403 -0
  78. package/out/internal/network/codec.d.ts +63 -0
  79. package/out/internal/network/codec.luau +1183 -0
  80. package/out/internal/network/envelope.d.ts +1 -0
  81. package/out/internal/network/envelope.luau +247 -0
  82. package/out/internal/network/limits.d.ts +26 -0
  83. package/out/internal/network/limits.luau +55 -0
  84. package/out/internal/network/port.d.ts +1 -0
  85. package/out/internal/network/port.luau +19 -0
  86. package/out/internal/network/roblox-network-port.d.ts +1 -0
  87. package/out/internal/network/roblox-network-port.luau +105 -0
  88. package/out/internal/network/schema.d.ts +72 -0
  89. package/out/internal/network/schema.luau +386 -0
  90. package/out/internal/network/server-session.d.ts +1 -0
  91. package/out/internal/network/server-session.luau +393 -0
  92. package/out/internal/network/session.d.ts +1 -0
  93. package/out/internal/network/session.luau +266 -0
  94. package/out/internal/reactive/context.d.ts +1 -0
  95. package/out/internal/reactive/context.luau +149 -0
  96. package/out/internal/reactive/flush.d.ts +1 -0
  97. package/out/internal/reactive/flush.luau +355 -0
  98. package/out/internal/reactive/graph.d.ts +1 -0
  99. package/out/internal/reactive/graph.luau +626 -0
  100. package/out/internal/reactive/limits.d.ts +1 -0
  101. package/out/internal/reactive/limits.luau +54 -0
  102. package/out/internal/reactive/ownership.d.ts +1 -0
  103. package/out/internal/reactive/ownership.luau +626 -0
  104. package/out/internal/registry.luau +10 -1
  105. package/out/internal/sync/client-sync.d.ts +1 -0
  106. package/out/internal/sync/client-sync.luau +157 -0
  107. package/out/internal/sync/declarations.d.ts +1 -0
  108. package/out/internal/sync/declarations.luau +276 -0
  109. package/out/internal/sync/frame.d.ts +1 -0
  110. package/out/internal/sync/frame.luau +91 -0
  111. package/out/internal/sync/server-sync.d.ts +1 -0
  112. package/out/internal/sync/server-sync.luau +502 -0
  113. package/out/internal/sync/values.d.ts +1 -0
  114. package/out/internal/sync/values.luau +930 -0
  115. package/out/network.d.ts +66 -0
  116. package/out/network.luau +24 -0
  117. package/out/reactive.d.ts +76 -0
  118. package/out/reactive.luau +136 -0
  119. package/out/replicated.d.ts +38 -0
  120. package/out/replicated.luau +45 -0
  121. package/out/server/data.d.ts +54 -0
  122. package/out/server/data.luau +44 -0
  123. package/out/server/index.d.ts +16 -0
  124. package/out/server/init.luau +49 -3
  125. package/out/server/publish.d.ts +20 -0
  126. package/out/server/publish.luau +24 -0
  127. package/out/vide/index.d.ts +7 -0
  128. package/out/vide/init.luau +50 -0
  129. package/package.json +13 -2
  130. package/transformer/data.cjs +72 -0
  131. package/transformer/declarations.cjs +153 -0
  132. package/transformer/index.js +37 -11
  133. package/transformer/network.cjs +961 -0
  134. package/vide/package.json +4 -0
@@ -0,0 +1,930 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _codec = TS.import(script, script.Parent.Parent, "network", "codec")
4
+ local checkFrame = _codec.frame
5
+ local validString = _codec.validString
6
+ local _limits = TS.import(script, script.Parent.Parent, "reactive", "limits")
7
+ local SYNC_ADMISSION_VISITS = _limits.SYNC_ADMISSION_VISITS
8
+ local SYNC_FRAME_BYTES = _limits.SYNC_FRAME_BYTES
9
+ local SYNC_FRAME_COLLECTION_ENTRIES = _limits.SYNC_FRAME_COLLECTION_ENTRIES
10
+ local SYNC_FRAME_VALUE_VISITS = _limits.SYNC_FRAME_VALUE_VISITS
11
+ local SYNC_STRING_BYTES = _limits.SYNC_STRING_BYTES
12
+ local SYNC_VALUE_DEPTH = _limits.SYNC_VALUE_DEPTH
13
+ --* @internal What the dynamic codec charges for a value: wire.md's bytes, entries and visits, and table nesting.
14
+ --* @internal A peer examination's visit budget. A visit that would pass `limit` is not made.
15
+ --* @internal Thrown when a visit would pass a `Work` limit: the peer is over budget, not the value.
16
+ local OVER_BUDGET = table.freeze({
17
+ overBudget = true,
18
+ })
19
+ --* @internal
20
+ local function isOverBudget(err)
21
+ return rawequal(err, OVER_BUDGET)
22
+ end
23
+ --* @internal Charges one visit to `work`, checked before it is made.
24
+ local function spend(work)
25
+ if work.visits >= work.limit then
26
+ error(OVER_BUDGET)
27
+ end
28
+ work.visits += 1
29
+ end
30
+ local function weak()
31
+ return setmetatable({}, {
32
+ __mode = "k",
33
+ })
34
+ end
35
+ --* @internal A descriptor compiled for admission: lookups by name and by discriminant, and a conformance cache.
36
+ local RUNTIME_KINDS = {
37
+ bool = "boolean",
38
+ f64 = "number",
39
+ f32 = "number",
40
+ u8 = "number",
41
+ u16 = "number",
42
+ u32 = "number",
43
+ string = "string",
44
+ vector3 = "Vector3",
45
+ color3 = "Color3",
46
+ cframe = "CFrame",
47
+ }
48
+ local function admitsNil(plan)
49
+ return plan.kind == "nil" or (plan.kind == "union" and plan["nil"] ~= nil)
50
+ end
51
+ --* @internal Compiles a descriptor the codec already accepted. Shared subtrees compile once.
52
+ local function compilePlan(root)
53
+ local memo = {}
54
+ local build
55
+ local function object(fields, extra)
56
+ local byName = {}
57
+ local order = {}
58
+ local optional = {}
59
+ for _, _binding in fields do
60
+ local name = _binding[1]
61
+ local child = _binding[2]
62
+ local plan = build(child)
63
+ byName[name] = plan
64
+ table.insert(order, name)
65
+ if admitsNil(plan) then
66
+ optional[name] = true
67
+ end
68
+ end
69
+ if extra ~= nil then
70
+ local _arg0 = extra[1]
71
+ local _arg1 = {
72
+ kind = "literal",
73
+ value = extra[2],
74
+ }
75
+ byName[_arg0] = _arg1
76
+ local _arg0_1 = extra[1]
77
+ table.insert(order, _arg0_1)
78
+ end
79
+ return {
80
+ kind = "object",
81
+ fields = byName,
82
+ order = order,
83
+ optional = optional,
84
+ conforms = weak(),
85
+ }
86
+ end
87
+ function build(node)
88
+ local _node = node
89
+ local known = memo[_node]
90
+ if known ~= nil then
91
+ return known
92
+ end
93
+ local plan
94
+ local _exp = node.kind
95
+ repeat
96
+ local _fallthrough = false
97
+ if _exp == "nil" then
98
+ _fallthrough = true
99
+ end
100
+ if _fallthrough or _exp == "bool" then
101
+ _fallthrough = true
102
+ end
103
+ if _fallthrough or _exp == "string" then
104
+ _fallthrough = true
105
+ end
106
+ if _fallthrough or _exp == "vector3" then
107
+ _fallthrough = true
108
+ end
109
+ if _fallthrough or _exp == "color3" then
110
+ _fallthrough = true
111
+ end
112
+ if _fallthrough or _exp == "cframe" then
113
+ plan = {
114
+ kind = node.kind,
115
+ }
116
+ break
117
+ end
118
+ if _exp == "f64" then
119
+ _fallthrough = true
120
+ end
121
+ if _fallthrough or _exp == "f32" then
122
+ _fallthrough = true
123
+ end
124
+ if _fallthrough or _exp == "u8" then
125
+ _fallthrough = true
126
+ end
127
+ if _fallthrough or _exp == "u16" then
128
+ _fallthrough = true
129
+ end
130
+ if _fallthrough or _exp == "u32" then
131
+ plan = {
132
+ kind = "number",
133
+ width = node.kind,
134
+ }
135
+ break
136
+ end
137
+ if _exp == "literal" then
138
+ plan = {
139
+ kind = "literal",
140
+ value = node.value,
141
+ }
142
+ break
143
+ end
144
+ if _exp == "instance" then
145
+ plan = {
146
+ kind = "refuse",
147
+ reason = "replicated state cannot hold Instances; replicate a stable id",
148
+ }
149
+ break
150
+ end
151
+ if _exp == "dynamic" then
152
+ plan = {
153
+ kind = "refuse",
154
+ reason = "replicated state cannot hold an unknown value",
155
+ }
156
+ break
157
+ end
158
+ if _exp == "object" then
159
+ plan = object(node.fields)
160
+ break
161
+ end
162
+ if _exp == "variant" then
163
+ plan = object(node.body.fields, { node.key, node.value })
164
+ break
165
+ end
166
+ if _exp == "array" then
167
+ plan = {
168
+ kind = "array",
169
+ element = build(node.element),
170
+ conforms = weak(),
171
+ }
172
+ break
173
+ end
174
+ if _exp == "tuple" then
175
+ local _object = {
176
+ kind = "tuple",
177
+ }
178
+ local _left = "elements"
179
+ local _exp_1 = node.elements
180
+ -- ▼ ReadonlyArray.map ▼
181
+ local _newValue = table.create(#_exp_1)
182
+ for _k, _v in _exp_1 do
183
+ _newValue[_k] = build(_v, _k - 1, _exp_1)
184
+ end
185
+ -- ▲ ReadonlyArray.map ▲
186
+ _object[_left] = _newValue
187
+ _object.conforms = weak()
188
+ plan = _object
189
+ break
190
+ end
191
+ if _exp == "map" then
192
+ local keys
193
+ if node.key.kind == "union" then
194
+ keys = {}
195
+ for _, branch in node.key.branches do
196
+ local _keys = keys
197
+ local _arg0 = tostring(branch.value)
198
+ _keys[_arg0] = true
199
+ end
200
+ end
201
+ plan = {
202
+ kind = "map",
203
+ keys = keys,
204
+ value = build(node.value),
205
+ conforms = weak(),
206
+ }
207
+ break
208
+ end
209
+ if _exp == "union" then
210
+ local literals = {}
211
+ local variants = {}
212
+ local kinds = {}
213
+ local absent
214
+ local discriminant
215
+ for _, branch in node.branches do
216
+ local child = build(branch)
217
+ if branch.kind == "nil" then
218
+ absent = child
219
+ elseif branch.kind == "literal" then
220
+ local _value = branch.value
221
+ literals[_value] = child
222
+ elseif branch.kind == "variant" then
223
+ discriminant = branch.key
224
+ local _value = branch.value
225
+ variants[_value] = child
226
+ else
227
+ local _condition = RUNTIME_KINDS[branch.kind]
228
+ if _condition == nil then
229
+ _condition = "table"
230
+ end
231
+ kinds[_condition] = child
232
+ end
233
+ end
234
+ plan = {
235
+ kind = "union",
236
+ ["nil"] = absent,
237
+ literals = literals,
238
+ discriminant = discriminant,
239
+ variants = variants,
240
+ kinds = kinds,
241
+ }
242
+ break
243
+ end
244
+ until true
245
+ local _node_1 = node
246
+ local _plan = plan
247
+ memo[_node_1] = _plan
248
+ return plan
249
+ end
250
+ return build(root)
251
+ end
252
+ local costs = setmetatable({}, {
253
+ __mode = "k",
254
+ })
255
+ local function varuintSize(value)
256
+ local size = 1
257
+ while value >= 128 do
258
+ value = math.floor(value / 128)
259
+ size += 1
260
+ end
261
+ return size
262
+ end
263
+ local function stringBytes(text)
264
+ return varuintSize(#text) + #text
265
+ end
266
+ --* @internal A leaf's dynamic cost, or an admitted table's cached cost.
267
+ local function costOf(value)
268
+ local _value = value
269
+ if type(_value) == "table" then
270
+ local _value_1 = value
271
+ local _condition = costs[_value_1]
272
+ if _condition == nil then
273
+ _condition = error("Crucible sync: costing a value admission never saw")
274
+ end
275
+ return _condition
276
+ end
277
+ local bytes = 1
278
+ local _value_1 = value
279
+ if type(_value_1) == "number" then
280
+ bytes = 9
281
+ else
282
+ local _value_2 = value
283
+ if type(_value_2) == "string" then
284
+ bytes = 1 + stringBytes(value)
285
+ else
286
+ local _value_3 = value
287
+ local _condition = typeof(_value_3) == "Vector3"
288
+ if not _condition then
289
+ local _value_4 = value
290
+ _condition = typeof(_value_4) == "Color3"
291
+ end
292
+ if _condition then
293
+ bytes = 25
294
+ else
295
+ local _value_4 = value
296
+ if typeof(_value_4) == "CFrame" then
297
+ bytes = 97
298
+ end
299
+ end
300
+ end
301
+ end
302
+ return {
303
+ bytes = bytes,
304
+ entries = 0,
305
+ visits = 1,
306
+ height = 0,
307
+ }
308
+ end
309
+ local function describe(value)
310
+ local _result
311
+ if value == nil then
312
+ _result = "nil"
313
+ else
314
+ local _value = value
315
+ _result = typeof(_value)
316
+ end
317
+ return _result
318
+ end
319
+ local Admission
320
+ do
321
+ Admission = setmetatable({}, {
322
+ __tostring = function()
323
+ return "Admission"
324
+ end,
325
+ })
326
+ Admission.__index = Admission
327
+ function Admission.new(...)
328
+ local self = setmetatable({}, Admission)
329
+ return self:constructor(...) or self
330
+ end
331
+ function Admission:constructor(name, work)
332
+ self.name = name
333
+ self.work = work
334
+ self.steps = 0
335
+ self.bytes = 0
336
+ self.entries = 0
337
+ self.visits = 0
338
+ self.depth = 0
339
+ self.height = 0
340
+ self.active = {}
341
+ self.pending = {}
342
+ end
343
+ function Admission:reject(path, reason)
344
+ error(`{self.name}{path}: {reason}`, 0)
345
+ end
346
+ function Admission:step(path)
347
+ if self.work ~= nil then
348
+ spend(self.work)
349
+ end
350
+ if self.steps >= SYNC_ADMISSION_VISITS then
351
+ self:reject(path, "over SYNC_ADMISSION_VISITS")
352
+ end
353
+ self.steps += 1
354
+ end
355
+ function Admission:charge(bytes, entries, visits, path)
356
+ self.bytes += bytes
357
+ self.entries += entries
358
+ self.visits += visits
359
+ if self.bytes > SYNC_FRAME_BYTES then
360
+ self:reject(path, "over SYNC_FRAME_BYTES")
361
+ end
362
+ if self.entries > SYNC_FRAME_COLLECTION_ENTRIES then
363
+ self:reject(path, "over SYNC_FRAME_COLLECTION_ENTRIES")
364
+ end
365
+ if self.visits > SYNC_FRAME_VALUE_VISITS then
366
+ self:reject(path, "over SYNC_FRAME_VALUE_VISITS")
367
+ end
368
+ end
369
+ function Admission:chargeLeaf(value, path)
370
+ local cost = costOf(value)
371
+ self:charge(cost.bytes, 0, 1, path)
372
+ end
373
+ function Admission:open(plan, value, path)
374
+ local _value = value
375
+ if not (type(_value) == "table") then
376
+ self:reject(path, `expected table, got {describe(value)}`)
377
+ end
378
+ if getmetatable(value) ~= nil then
379
+ self:reject(path, "metatable")
380
+ end
381
+ local _active = self.active
382
+ local _value_1 = value
383
+ if _active[_value_1] ~= nil then
384
+ self:reject(path, "cycle")
385
+ end
386
+ local _condition = table.isfrozen(value)
387
+ if _condition then
388
+ local _conforms = plan.conforms
389
+ local _value_2 = value
390
+ _condition = _conforms[_value_2] ~= nil
391
+ end
392
+ if _condition then
393
+ local _value_2 = value
394
+ local cost = costs[_value_2]
395
+ if self.depth + cost.height > SYNC_VALUE_DEPTH then
396
+ self:reject(path, "deeper than SYNC_VALUE_DEPTH")
397
+ end
398
+ self:charge(cost.bytes, cost.entries, cost.visits, path)
399
+ self.height = math.max(self.height, cost.height)
400
+ return value
401
+ end
402
+ return nil
403
+ end
404
+ function Admission:nest(plan, value, path, walk)
405
+ self.depth += 1
406
+ if self.depth > SYNC_VALUE_DEPTH then
407
+ self:reject(path, "deeper than SYNC_VALUE_DEPTH")
408
+ end
409
+ local _active = self.active
410
+ local _value = value
411
+ _active[_value] = true
412
+ local outer = self.height
413
+ self.height = 0
414
+ local bytes, entries, visits = self.bytes, self.entries, self.visits
415
+ local result = walk()
416
+ local height = self.height + 1
417
+ local _pending = self.pending
418
+ local _arg0 = {
419
+ plan = plan,
420
+ value = result,
421
+ cost = {
422
+ bytes = self.bytes - bytes,
423
+ entries = self.entries - entries,
424
+ visits = self.visits - visits,
425
+ height = height,
426
+ },
427
+ }
428
+ table.insert(_pending, _arg0)
429
+ self.height = math.max(outer, height)
430
+ local _active_1 = self.active
431
+ local _value_1 = value
432
+ _active_1[_value_1] = nil
433
+ self.depth -= 1
434
+ return result
435
+ end
436
+ end
437
+ local function admitNumber(a, width, value, path)
438
+ local _value = value
439
+ if not (type(_value) == "number") then
440
+ a:reject(path, `expected number, got {describe(value)}`)
441
+ end
442
+ if value ~= value or math.abs(value) == math.huge then
443
+ a:reject(path, "expected finite number")
444
+ end
445
+ if width == "f32" then
446
+ local scratch = buffer.create(4)
447
+ buffer.writef32(scratch, 0, value)
448
+ if math.abs(buffer.readf32(scratch, 0)) == math.huge then
449
+ a:reject(path, "f32 out of range")
450
+ end
451
+ elseif width ~= "f64" then
452
+ local maximum = if width == "u8" then 255 elseif width == "u16" then 65535 else 4294967295
453
+ if value < 0 or value > maximum or value % 1 ~= 0 then
454
+ a:reject(path, `{width} out of range`)
455
+ end
456
+ end
457
+ end
458
+ local function admitString(a, value, path)
459
+ local _value = value
460
+ if not (type(_value) == "string") then
461
+ a:reject(path, `expected string, got {describe(value)}`)
462
+ end
463
+ if #value > SYNC_STRING_BYTES then
464
+ a:reject(path, "longer than SYNC_STRING_BYTES")
465
+ end
466
+ local ok = pcall(validString, value)
467
+ if not ok then
468
+ a:reject(path, "invalid UTF-8")
469
+ end
470
+ end
471
+ local admitObject, admitArray, admitMap, admitUnion
472
+ local function admitNode(a, plan, value, path)
473
+ a:step(path)
474
+ local _exp = plan.kind
475
+ repeat
476
+ local _fallthrough = false
477
+ if _exp == "nil" then
478
+ if value ~= nil then
479
+ a:reject(path, `expected nil, got {describe(value)}`)
480
+ end
481
+ break
482
+ end
483
+ if _exp == "bool" then
484
+ local _value = value
485
+ if not (type(_value) == "boolean") then
486
+ a:reject(path, `expected boolean, got {describe(value)}`)
487
+ end
488
+ break
489
+ end
490
+ if _exp == "number" then
491
+ admitNumber(a, plan.width, value, path)
492
+ break
493
+ end
494
+ if _exp == "string" then
495
+ admitString(a, value, path)
496
+ break
497
+ end
498
+ if _exp == "literal" then
499
+ if value ~= plan.value then
500
+ a:reject(path, `expected a declared literal, got {describe(value)}`)
501
+ end
502
+ local _value = value
503
+ if type(_value) == "string" then
504
+ admitString(a, value, path)
505
+ end
506
+ break
507
+ end
508
+ if _exp == "vector3" then
509
+ local _value = value
510
+ if not (typeof(_value) == "Vector3") then
511
+ a:reject(path, `expected Vector3, got {describe(value)}`)
512
+ end
513
+ for _, component in { value.X, value.Y, value.Z } do
514
+ if component ~= component or math.abs(component) == math.huge then
515
+ a:reject(path, "expected finite number")
516
+ end
517
+ end
518
+ break
519
+ end
520
+ if _exp == "color3" then
521
+ local _value = value
522
+ if not (typeof(_value) == "Color3") then
523
+ a:reject(path, `expected Color3, got {describe(value)}`)
524
+ end
525
+ for _, component in { value.R, value.G, value.B } do
526
+ if component ~= component or math.abs(component) == math.huge then
527
+ a:reject(path, "expected finite number")
528
+ end
529
+ end
530
+ break
531
+ end
532
+ if _exp == "cframe" then
533
+ local _value = value
534
+ if not (typeof(_value) == "CFrame") then
535
+ a:reject(path, `expected CFrame, got {describe(value)}`)
536
+ end
537
+ local _array = {}
538
+ local _length = #_array
539
+ local _array_1 = { value:GetComponents() }
540
+ table.move(_array_1, 1, #_array_1, _length + 1, _array)
541
+ local ok = pcall(checkFrame, _array, path)
542
+ if not ok then
543
+ a:reject(path, "invalid CFrame")
544
+ end
545
+ break
546
+ end
547
+ if _exp == "refuse" then
548
+ return a:reject(path, plan.reason)
549
+ end
550
+ if _exp == "object" then
551
+ return admitObject(a, plan, value, path)
552
+ end
553
+ if _exp == "array" then
554
+ _fallthrough = true
555
+ end
556
+ if _fallthrough or _exp == "tuple" then
557
+ return admitArray(a, plan, value, path)
558
+ end
559
+ if _exp == "map" then
560
+ return admitMap(a, plan, value, path)
561
+ end
562
+ if _exp == "union" then
563
+ return admitUnion(a, plan, value, path)
564
+ end
565
+ until true
566
+ a:chargeLeaf(value, path)
567
+ return value
568
+ end
569
+ local function admitField(a, name, plan, value, path)
570
+ a:charge(stringBytes(name), 1, 1, path)
571
+ return admitNode(a, plan, value, `{path}.{name}`)
572
+ end
573
+ function admitObject(a, plan, value, path)
574
+ local cached = a:open(plan, value, path)
575
+ if cached ~= nil then
576
+ return cached
577
+ end
578
+ local source = value
579
+ return a:nest(plan, source, path, function()
580
+ a:charge(1, 0, 1, path)
581
+ local admitted = {}
582
+ local copy = false
583
+ local stopped = false
584
+ for key, child in pairs(source) do
585
+ a:step(path)
586
+ local field = if type(key) == "string" then plan.fields[key] else nil
587
+ if field == nil then
588
+ copy = true
589
+ stopped = true
590
+ break
591
+ end
592
+ local result = admitField(a, key, field, child, path)
593
+ if not rawequal(result, child) then
594
+ copy = true
595
+ end
596
+ admitted[key] = result
597
+ end
598
+ for _, name in plan.order do
599
+ if admitted[name] ~= nil then
600
+ continue
601
+ end
602
+ local child
603
+ if stopped then
604
+ a:step(path)
605
+ child = source[name]
606
+ end
607
+ if child == nil then
608
+ if not (plan.optional[name] ~= nil) then
609
+ a:reject(`{path}.{name}`, "missing")
610
+ end
611
+ continue
612
+ end
613
+ local result = admitField(a, name, plan.fields[name], child, path)
614
+ if not rawequal(result, child) then
615
+ copy = true
616
+ end
617
+ admitted[name] = result
618
+ end
619
+ local _a = a
620
+ -- ▼ ReadonlyMap.size ▼
621
+ local _size = 0
622
+ for _ in admitted do
623
+ _size += 1
624
+ end
625
+ -- ▲ ReadonlyMap.size ▲
626
+ _a:charge(varuintSize(_size), 0, 0, path)
627
+ if not copy then
628
+ return source
629
+ end
630
+ local out = {}
631
+ for name, child in admitted do
632
+ out[name] = child
633
+ end
634
+ return out
635
+ end)
636
+ end
637
+ function admitArray(a, plan, value, path)
638
+ local cached = a:open(plan, value, path)
639
+ if cached ~= nil then
640
+ return cached
641
+ end
642
+ local source = value
643
+ return a:nest(plan, source, path, function()
644
+ local count = #source
645
+ if plan.kind == "tuple" and count ~= #plan.elements then
646
+ a:reject(path, "tuple length")
647
+ end
648
+ a:charge(1 + varuintSize(count), count, 1, path)
649
+ local seen = 0
650
+ for key in pairs(source) do
651
+ a:step(path)
652
+ seen += 1
653
+ if seen > count or not (type(key) == "number") or key % 1 ~= 0 or key < 1 or key > count then
654
+ a:reject(path, "sparse array")
655
+ end
656
+ end
657
+ if seen ~= count then
658
+ a:reject(path, "sparse array")
659
+ end
660
+ local copy
661
+ do
662
+ local i = 0
663
+ local _shouldIncrement = false
664
+ while true do
665
+ if _shouldIncrement then
666
+ i += 1
667
+ else
668
+ _shouldIncrement = true
669
+ end
670
+ if not (i < count) then
671
+ break
672
+ end
673
+ local child = source[i + 1]
674
+ local result = admitNode(a, if plan.kind == "array" then plan.element else plan.elements[i + 1], child, `{path}[{i + 1}]`)
675
+ if not rawequal(result, child) and copy == nil then
676
+ copy = table.clone(source)
677
+ end
678
+ if copy ~= nil then
679
+ copy[i + 1] = result
680
+ end
681
+ end
682
+ end
683
+ return copy or source
684
+ end)
685
+ end
686
+ function admitMap(a, plan, value, path)
687
+ local cached = a:open(plan, value, path)
688
+ if cached ~= nil then
689
+ return cached
690
+ end
691
+ local source = value
692
+ return a:nest(plan, source, path, function()
693
+ a:charge(1, 0, 1, path)
694
+ local count = 0
695
+ local copy
696
+ for key, child in pairs(source) do
697
+ a:step(path)
698
+ if not (type(key) == "string") or (plan.keys ~= nil and not (plan.keys[key] ~= nil)) then
699
+ a:reject(path, "invalid map key")
700
+ end
701
+ count += 1
702
+ a:charge(stringBytes(key), 1, 1, path)
703
+ local result = admitNode(a, plan.value, child, `{path}[]`)
704
+ if not rawequal(result, child) and copy == nil then
705
+ copy = table.clone(source)
706
+ end
707
+ if copy ~= nil then
708
+ copy[key] = result
709
+ end
710
+ end
711
+ a:charge(varuintSize(count), 0, 0, path)
712
+ return copy or source
713
+ end)
714
+ end
715
+ function admitUnion(a, plan, value, path)
716
+ if value == nil then
717
+ if plan["nil"] == nil then
718
+ a:reject(path, "no branch for nil")
719
+ end
720
+ return admitNode(a, plan["nil"], value, path)
721
+ end
722
+ local _literals = plan.literals
723
+ local _value = value
724
+ local literal = _literals[_value]
725
+ if literal ~= nil then
726
+ return admitNode(a, literal, value, path)
727
+ end
728
+ local key = plan.discriminant
729
+ local _condition = key ~= nil
730
+ if _condition then
731
+ local _value_1 = value
732
+ _condition = type(_value_1) == "table"
733
+ if _condition then
734
+ _condition = getmetatable(value) == nil
735
+ end
736
+ end
737
+ if _condition then
738
+ a:step(path)
739
+ local _variants = plan.variants
740
+ local _arg0 = value[key]
741
+ local variant = _variants[_arg0]
742
+ if variant == nil then
743
+ a:reject(path, "no variant for its discriminant")
744
+ end
745
+ return admitNode(a, variant, value, path)
746
+ end
747
+ local _kinds = plan.kinds
748
+ local _value_1 = value
749
+ local _arg0 = typeof(_value_1)
750
+ local branch = _kinds[_arg0]
751
+ if branch == nil then
752
+ a:reject(path, `no branch for {describe(value)}`)
753
+ end
754
+ return admitNode(a, branch, value, path)
755
+ end
756
+ --[[
757
+ *
758
+ * @internal
759
+ * Rule 49. Projects `value` through `plan`: undeclared keys are dropped unread, declared positions are checked, and
760
+ * the result is frozen. Throws "<name><path>: <reason>" for a rejected value, never the value itself, and
761
+ * `OverBudget` when a visit would pass `work`'s limit.
762
+
763
+ ]]
764
+ local function admit(plan, name, value, work)
765
+ local a = Admission.new(name, work)
766
+ local result = admitNode(a, plan, value, "")
767
+ for _, entry in a.pending do
768
+ if not table.isfrozen(entry.value) then
769
+ table.freeze(entry.value)
770
+ end
771
+ local _value = entry.value
772
+ local _cost = entry.cost
773
+ costs[_value] = _cost
774
+ local _conforms = entry.plan.conforms
775
+ local _value_1 = entry.value
776
+ _conforms[_value_1] = true
777
+ end
778
+ return result
779
+ end
780
+ local BOUND = table.freeze({
781
+ bounded = true,
782
+ })
783
+ --[[
784
+ *
785
+ * @internal
786
+ * Rule 61. Structural equality of two admitted values, free across `rawequal` subtrees. One visit per node pair and
787
+ * per key iterated, charged to `work`; past SYNC_FRAME_VALUE_VISITS it answers not equal.
788
+
789
+ ]]
790
+ local function equal(a, b, work)
791
+ local spent = 0
792
+ local function visit()
793
+ if work ~= nil then
794
+ spend(work)
795
+ end
796
+ spent += 1
797
+ if spent > SYNC_FRAME_VALUE_VISITS then
798
+ error(BOUND)
799
+ end
800
+ end
801
+ local function same(x, y)
802
+ visit()
803
+ if rawequal(x, y) then
804
+ return true
805
+ end
806
+ local _x = x
807
+ local _condition = not (type(_x) == "table")
808
+ if not _condition then
809
+ local _y = y
810
+ _condition = not (type(_y) == "table")
811
+ end
812
+ if _condition then
813
+ local _x_1 = x
814
+ local _exp = typeof(_x_1)
815
+ local _y = y
816
+ local _condition_1 = _exp == typeof(_y)
817
+ if _condition_1 then
818
+ _condition_1 = x == y
819
+ end
820
+ return _condition_1
821
+ end
822
+ local xs = x
823
+ local ys = y
824
+ if #xs > 0 or #ys > 0 then
825
+ if #xs ~= #ys then
826
+ return false
827
+ end
828
+ for i = 0, #ys - 1 do
829
+ visit()
830
+ if not same(xs[i + 1], ys[i + 1]) then
831
+ return false
832
+ end
833
+ end
834
+ return true
835
+ end
836
+ local xr = x
837
+ local count = 0
838
+ for key, child in pairs(y) do
839
+ visit()
840
+ count += 1
841
+ local other = xr[key]
842
+ if other == nil or not same(other, child) then
843
+ return false
844
+ end
845
+ end
846
+ local seen = 0
847
+ for _element, _element_1 in pairs(xr) do
848
+ local _ = { _element, _element_1 }
849
+ visit()
850
+ seen += 1
851
+ if seen > count then
852
+ return false
853
+ end
854
+ end
855
+ return seen == count
856
+ end
857
+ local ok, result = pcall(same, a, b)
858
+ if ok then
859
+ return result
860
+ end
861
+ if rawequal(result, BOUND) then
862
+ return false
863
+ end
864
+ error(result, 0)
865
+ end
866
+ --[[
867
+ *
868
+ * @internal
869
+ * Rule 63, client. The value to store for a `decoded` one: every subtree structurally equal to the matching subtree
870
+ * of `current` is that subtree, so unchanged parts keep their identity. Returns `current` when the whole value is
871
+ * equal. `decoded` is a fresh tree, so its new tables are frozen in place.
872
+
873
+ ]]
874
+ local function reuse(current, decoded)
875
+ local _decoded = decoded
876
+ if not (type(_decoded) == "table") then
877
+ local _condition = rawequal(current, decoded)
878
+ if not _condition then
879
+ local _current = current
880
+ local _exp = typeof(_current)
881
+ local _decoded_1 = decoded
882
+ local _condition_1 = _exp == typeof(_decoded_1)
883
+ if _condition_1 then
884
+ _condition_1 = current == decoded
885
+ end
886
+ _condition = _condition_1
887
+ end
888
+ return if _condition then current else decoded
889
+ end
890
+ local _current = current
891
+ local old = if type(_current) == "table" then current else nil
892
+ local fresh = decoded
893
+ local same = old ~= nil
894
+ local count = 0
895
+ for key, child in pairs(fresh) do
896
+ count += 1
897
+ local _before = old
898
+ if _before ~= nil then
899
+ _before = _before[key]
900
+ end
901
+ local before = _before
902
+ local result = reuse(before, child)
903
+ if not rawequal(result, child) then
904
+ fresh[key] = result
905
+ end
906
+ if before == nil or not rawequal(result, before) then
907
+ same = false
908
+ end
909
+ end
910
+ if same then
911
+ local seen = 0
912
+ for _element, _element_1 in pairs(old) do
913
+ local _ = { _element, _element_1 }
914
+ seen += 1
915
+ end
916
+ if seen == count then
917
+ return old
918
+ end
919
+ end
920
+ return table.freeze(fresh)
921
+ end
922
+ return {
923
+ isOverBudget = isOverBudget,
924
+ spend = spend,
925
+ compilePlan = compilePlan,
926
+ costOf = costOf,
927
+ admit = admit,
928
+ equal = equal,
929
+ reuse = reuse,
930
+ }