@trioforge/crucible 0.1.0 → 0.3.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 (145) hide show
  1. package/maths/spline/package.json +4 -0
  2. package/out/client/index.d.ts +7 -0
  3. package/out/client/init.luau +36 -1
  4. package/out/configs.d.ts +37 -0
  5. package/out/configs.luau +46 -0
  6. package/out/data.d.ts +224 -0
  7. package/out/data.luau +97 -0
  8. package/out/emit/configs.d.ts +4 -0
  9. package/out/emit/configs.luau +22 -0
  10. package/out/emit/data.d.ts +6 -0
  11. package/out/emit/data.luau +19 -0
  12. package/out/emit/index.d.ts +5 -1
  13. package/out/emit/init.luau +23 -7
  14. package/out/emit/network.d.ts +6 -0
  15. package/out/emit/network.luau +36 -0
  16. package/out/emit/replicated.d.ts +4 -0
  17. package/out/emit/replicated.luau +13 -0
  18. package/out/index.d.ts +8 -0
  19. package/out/init.luau +23 -0
  20. package/out/internal/binding.luau +5 -0
  21. package/out/internal/boot.luau +112 -8
  22. package/out/internal/configs/limits.d.ts +10 -0
  23. package/out/internal/configs/limits.luau +25 -0
  24. package/out/internal/configs/port.d.ts +29 -0
  25. package/out/internal/configs/port.luau +99 -0
  26. package/out/internal/configs/runtime.d.ts +21 -0
  27. package/out/internal/configs/runtime.luau +981 -0
  28. package/out/internal/data/admission.d.ts +1 -0
  29. package/out/internal/data/admission.luau +673 -0
  30. package/out/internal/data/declarations.d.ts +1 -0
  31. package/out/internal/data/declarations.luau +384 -0
  32. package/out/internal/data/envelope.d.ts +1 -0
  33. package/out/internal/data/envelope.luau +318 -0
  34. package/out/internal/data/handles.d.ts +1 -0
  35. package/out/internal/data/handles.luau +255 -0
  36. package/out/internal/data/inbox-transforms.d.ts +1 -0
  37. package/out/internal/data/inbox-transforms.luau +458 -0
  38. package/out/internal/data/inbox.d.ts +1 -0
  39. package/out/internal/data/inbox.luau +437 -0
  40. package/out/internal/data/lease.d.ts +1 -0
  41. package/out/internal/data/lease.luau +304 -0
  42. package/out/internal/data/limits.d.ts +1 -0
  43. package/out/internal/data/limits.luau +234 -0
  44. package/out/internal/data/messages.d.ts +1 -0
  45. package/out/internal/data/messages.luau +69 -0
  46. package/out/internal/data/operation-id.d.ts +1 -0
  47. package/out/internal/data/operation-id.luau +61 -0
  48. package/out/internal/data/paths.d.ts +2 -0
  49. package/out/internal/data/paths.luau +323 -0
  50. package/out/internal/data/port.d.ts +1 -0
  51. package/out/internal/data/port.luau +290 -0
  52. package/out/internal/data/profiles.d.ts +1 -0
  53. package/out/internal/data/profiles.luau +862 -0
  54. package/out/internal/data/receipts.d.ts +1 -0
  55. package/out/internal/data/receipts.luau +190 -0
  56. package/out/internal/data/record-bindings.d.ts +1 -0
  57. package/out/internal/data/record-bindings.luau +92 -0
  58. package/out/internal/data/record-declarations.d.ts +1 -0
  59. package/out/internal/data/record-declarations.luau +140 -0
  60. package/out/internal/data/records.d.ts +1 -0
  61. package/out/internal/data/records.luau +400 -0
  62. package/out/internal/data/runtime.d.ts +1 -0
  63. package/out/internal/data/runtime.luau +918 -0
  64. package/out/internal/data/scheduler.d.ts +1 -0
  65. package/out/internal/data/scheduler.luau +507 -0
  66. package/out/internal/data/session.d.ts +1 -0
  67. package/out/internal/data/session.luau +234 -0
  68. package/out/internal/data/shutdown.d.ts +1 -0
  69. package/out/internal/data/shutdown.luau +91 -0
  70. package/out/internal/data/waiters.d.ts +1 -0
  71. package/out/internal/data/waiters.luau +65 -0
  72. package/out/internal/lifecycle.luau +45 -0
  73. package/out/internal/network/catalog.d.ts +26 -0
  74. package/out/internal/network/catalog.luau +182 -0
  75. package/out/internal/network/claims.d.ts +1 -0
  76. package/out/internal/network/claims.luau +146 -0
  77. package/out/internal/network/client-session.d.ts +1 -0
  78. package/out/internal/network/client-session.luau +405 -0
  79. package/out/internal/network/codec.d.ts +63 -0
  80. package/out/internal/network/codec.luau +1183 -0
  81. package/out/internal/network/envelope.d.ts +1 -0
  82. package/out/internal/network/envelope.luau +247 -0
  83. package/out/internal/network/limits.d.ts +26 -0
  84. package/out/internal/network/limits.luau +55 -0
  85. package/out/internal/network/port.d.ts +1 -0
  86. package/out/internal/network/port.luau +19 -0
  87. package/out/internal/network/roblox-network-port.d.ts +1 -0
  88. package/out/internal/network/roblox-network-port.luau +105 -0
  89. package/out/internal/network/schema.d.ts +72 -0
  90. package/out/internal/network/schema.luau +386 -0
  91. package/out/internal/network/server-session.d.ts +1 -0
  92. package/out/internal/network/server-session.luau +395 -0
  93. package/out/internal/network/session.d.ts +1 -0
  94. package/out/internal/network/session.luau +266 -0
  95. package/out/internal/reactive/context.d.ts +1 -0
  96. package/out/internal/reactive/context.luau +149 -0
  97. package/out/internal/reactive/flush.d.ts +1 -0
  98. package/out/internal/reactive/flush.luau +355 -0
  99. package/out/internal/reactive/graph.d.ts +1 -0
  100. package/out/internal/reactive/graph.luau +626 -0
  101. package/out/internal/reactive/limits.d.ts +1 -0
  102. package/out/internal/reactive/limits.luau +54 -0
  103. package/out/internal/reactive/ownership.d.ts +1 -0
  104. package/out/internal/reactive/ownership.luau +626 -0
  105. package/out/internal/registry.luau +10 -1
  106. package/out/internal/sync/client-sync.d.ts +1 -0
  107. package/out/internal/sync/client-sync.luau +157 -0
  108. package/out/internal/sync/declarations.d.ts +1 -0
  109. package/out/internal/sync/declarations.luau +276 -0
  110. package/out/internal/sync/frame.d.ts +1 -0
  111. package/out/internal/sync/frame.luau +91 -0
  112. package/out/internal/sync/server-sync.d.ts +1 -0
  113. package/out/internal/sync/server-sync.luau +502 -0
  114. package/out/internal/sync/values.d.ts +1 -0
  115. package/out/internal/sync/values.luau +930 -0
  116. package/out/maths/spline/curve.d.ts +35 -0
  117. package/out/maths/spline/curve.luau +1122 -0
  118. package/out/maths/spline/index.d.ts +2 -0
  119. package/out/maths/spline/init.luau +8 -0
  120. package/out/network.d.ts +66 -0
  121. package/out/network.luau +24 -0
  122. package/out/reactive.d.ts +76 -0
  123. package/out/reactive.luau +136 -0
  124. package/out/replicated.d.ts +38 -0
  125. package/out/replicated.luau +45 -0
  126. package/out/server/data.d.ts +54 -0
  127. package/out/server/data.luau +44 -0
  128. package/out/server/index.d.ts +17 -0
  129. package/out/server/init.luau +58 -4
  130. package/out/server/publish.d.ts +20 -0
  131. package/out/server/publish.luau +24 -0
  132. package/out/testing/index.d.ts +38 -0
  133. package/out/testing/init.luau +189 -0
  134. package/out/util/random/index.d.ts +24 -0
  135. package/out/util/random/init.luau +301 -0
  136. package/out/vide/index.d.ts +7 -0
  137. package/out/vide/init.luau +50 -0
  138. package/package.json +28 -2
  139. package/testing/package.json +4 -0
  140. package/transformer/data.cjs +72 -0
  141. package/transformer/declarations.cjs +153 -0
  142. package/transformer/index.js +87 -23
  143. package/transformer/network.cjs +961 -0
  144. package/util/random/package.json +4 -0
  145. package/vide/package.json +4 -0
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,673 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local call = TS.import(script, script.Parent.Parent, "runner").call
4
+ local _schema = TS.import(script, script.Parent.Parent, "network", "schema")
5
+ local admitsNil = _schema.admitsNil
6
+ local n = _schema.n
7
+ local runtimeKind = _schema.runtimeKind
8
+ local _limits = TS.import(script, script.Parent, "limits")
9
+ local PERSIST_ADMISSION_VISITS = _limits.PERSIST_ADMISSION_VISITS
10
+ local PERSIST_DEPTH = _limits.PERSIST_DEPTH
11
+ local PERSIST_STRING_BYTES = _limits.PERSIST_STRING_BYTES
12
+ --* @internal A schema migration: a private copy of the stored record in, a patch out (rule 75).
13
+ --* @internal What the durability engine knows of a profile or record schema.
14
+ --* @internal
15
+ --* @internal
16
+ local OPEN = n.object({})
17
+ local FAILURE = {}
18
+ local JSON_NODE = {}
19
+ local F32_MAX = 3.4028234663852886e38
20
+ local UINT_MAX = {
21
+ u8 = 255,
22
+ u16 = 65_535,
23
+ u32 = 4_294_967_295,
24
+ }
25
+ --* A frozen table admitted under a node is not walked again (rule 15).
26
+ local admitted = setmetatable({}, {
27
+ __mode = "k",
28
+ })
29
+ local function fail(path, reason)
30
+ error({ FAILURE, path, reason })
31
+ end
32
+ --* The length of a table whose keys are exactly 1..n, and undefined for any other table.
33
+ local function denseLength(value)
34
+ local items = value
35
+ local length = #items
36
+ local count = 0
37
+ for key in pairs(value) do
38
+ if not (type(key) == "number") or key % 1 ~= 0 or key < 1 or key > length then
39
+ return nil
40
+ end
41
+ count += 1
42
+ end
43
+ return if count == length then length else nil
44
+ end
45
+ local function hasOnlyStringKeys(value)
46
+ for key in pairs(value) do
47
+ if not (type(key) == "string") then
48
+ return false
49
+ end
50
+ end
51
+ return true
52
+ end
53
+ local function describe(node)
54
+ local _exp = node.kind
55
+ repeat
56
+ local _fallthrough = false
57
+ if _exp == "bool" then
58
+ return "boolean"
59
+ end
60
+ if _exp == "string" then
61
+ return "string"
62
+ end
63
+ if _exp == "object" then
64
+ _fallthrough = true
65
+ end
66
+ if _fallthrough or _exp == "map" then
67
+ return "an object"
68
+ end
69
+ if _exp == "array" then
70
+ _fallthrough = true
71
+ end
72
+ if _fallthrough or _exp == "tuple" then
73
+ return "an array"
74
+ end
75
+ if _exp == "literal" then
76
+ return `the literal {tostring(node.value)}`
77
+ end
78
+ if _exp == "variant" then
79
+ return `the variant {node.key} = {tostring(node.value)}`
80
+ end
81
+ if _exp == "union" then
82
+ local _exp_1 = node.branches
83
+ -- ▼ ReadonlyArray.map ▼
84
+ local _newValue = table.create(#_exp_1)
85
+ for _k, _v in _exp_1 do
86
+ _newValue[_k] = describe(_v, _k - 1, _exp_1)
87
+ end
88
+ -- ▲ ReadonlyArray.map ▲
89
+ return table.concat(_newValue, " or ")
90
+ end
91
+ if _exp == "nil" then
92
+ return "nothing"
93
+ end
94
+ return "number"
95
+ until true
96
+ end
97
+ local function run(root, rootValue, rootDefault)
98
+ local visits = 0
99
+ --* Height of the subtree the last walk returned: 0 for a scalar, 1 plus its deepest child for a table.
100
+ local height = 0
101
+ local open = {}
102
+ local function count(path)
103
+ visits += 1
104
+ if visits > PERSIST_ADMISSION_VISITS then
105
+ fail(path, `more than {PERSIST_ADMISSION_VISITS} values`)
106
+ end
107
+ end
108
+ local function enter(value, path, depth)
109
+ count(path)
110
+ if depth > PERSIST_DEPTH then
111
+ fail(path, `deeper than {PERSIST_DEPTH}`)
112
+ end
113
+ if getmetatable(value) ~= nil then
114
+ fail(path, "a table with a metatable")
115
+ end
116
+ local _value = value
117
+ if open[_value] ~= nil then
118
+ fail(path, "a cycle")
119
+ end
120
+ local _value_1 = value
121
+ open[_value_1] = true
122
+ end
123
+ --* Reuses a frozen table already admitted under `node`, at its cached cost and with its depth checked.
124
+ local function known(value, node, path, depth)
125
+ local _value = value
126
+ local hit = admitted[_value]
127
+ if hit == nil or hit.node ~= node or not table.isfrozen(value) then
128
+ return false
129
+ end
130
+ visits += hit.visits
131
+ if visits > PERSIST_ADMISSION_VISITS then
132
+ fail(path, `more than {PERSIST_ADMISSION_VISITS} values`)
133
+ end
134
+ if depth + hit.height - 1 > PERSIST_DEPTH then
135
+ fail(path, `deeper than {PERSIST_DEPTH}`)
136
+ end
137
+ height = hit.height
138
+ return true
139
+ end
140
+ local function finish(source, copy, node, before, top, same)
141
+ local _source = source
142
+ open[_source] = nil
143
+ local result = if same and table.isfrozen(source) then source else table.freeze(copy)
144
+ height = top + 1
145
+ local _arg1 = {
146
+ node = node,
147
+ visits = visits - before,
148
+ height = height,
149
+ }
150
+ admitted[result] = _arg1
151
+ return result
152
+ end
153
+ local function scalar(value, path)
154
+ count(path)
155
+ height = 0
156
+ return value
157
+ end
158
+ local function text(value, path)
159
+ local _value = value
160
+ if not (type(_value) == "string") then
161
+ local _exp = path
162
+ local _value_1 = value
163
+ fail(_exp, `expected string, got {typeof(_value_1)}`)
164
+ end
165
+ if #value > PERSIST_STRING_BYTES then
166
+ fail(path, `a string over {PERSIST_STRING_BYTES} bytes`)
167
+ end
168
+ if (utf8.len(value)) == nil then
169
+ fail(path, "invalid UTF-8")
170
+ end
171
+ return value
172
+ end
173
+ local function number(value, path)
174
+ local _value = value
175
+ if not (type(_value) == "number") then
176
+ local _exp = path
177
+ local _value_1 = value
178
+ fail(_exp, `expected number, got {typeof(_value_1)}`)
179
+ end
180
+ if value ~= value or math.abs(value) == math.huge then
181
+ fail(path, "expected a finite number")
182
+ end
183
+ return value
184
+ end
185
+ --* An undeclared value: storable JSON, copied and frozen, never interpreted (rule 76).
186
+ local function json(value, path, depth)
187
+ local _value = value
188
+ if type(_value) == "string" then
189
+ return scalar(text(value, path), path)
190
+ end
191
+ local _value_1 = value
192
+ if type(_value_1) == "number" then
193
+ return scalar(number(value, path), path)
194
+ end
195
+ local _value_2 = value
196
+ if type(_value_2) == "boolean" then
197
+ return scalar(value, path)
198
+ end
199
+ local _value_3 = value
200
+ if not (type(_value_3) == "table") then
201
+ local _exp = path
202
+ local _value_4 = value
203
+ fail(_exp, `expected JSON, got {typeof(_value_4)}`)
204
+ end
205
+ if known(value, JSON_NODE, path, depth) then
206
+ return value
207
+ end
208
+ local before = visits
209
+ enter(value, path, depth)
210
+ local length = denseLength(value)
211
+ local copy = {}
212
+ local top = 0
213
+ local same = true
214
+ if length ~= nil and length > 0 then
215
+ local items = value
216
+ do
217
+ local i = 0
218
+ local _shouldIncrement = false
219
+ while true do
220
+ if _shouldIncrement then
221
+ i += 1
222
+ else
223
+ _shouldIncrement = true
224
+ end
225
+ if not (i < length) then
226
+ break
227
+ end
228
+ local child = json(items[i + 1], `{path}[{i}]`, depth + 1)
229
+ top = math.max(top, height)
230
+ if not rawequal(child, items[i + 1]) then
231
+ same = false
232
+ end
233
+ copy[i + 1] = child
234
+ end
235
+ end
236
+ else
237
+ if not hasOnlyStringKeys(value) then
238
+ fail(path, "a table mixing array and object keys")
239
+ end
240
+ for key, entry in pairs(value) do
241
+ local child = json(entry, `{path}.{key}`, depth + 1)
242
+ top = math.max(top, height)
243
+ if not rawequal(child, entry) then
244
+ same = false
245
+ end
246
+ copy[key] = child
247
+ end
248
+ end
249
+ return finish(value, copy, JSON_NODE, before, top, same)
250
+ end
251
+ local function tableOf(value, path, expected)
252
+ local _value = value
253
+ if not (type(_value) == "table") then
254
+ local _exp = path
255
+ local _exp_1 = expected
256
+ local _value_1 = value
257
+ fail(_exp, `expected {_exp_1}, got {typeof(_value_1)}`)
258
+ end
259
+ return value
260
+ end
261
+ local walk
262
+ local function object(node, value, fallback, path, depth, discriminant)
263
+ local source = tableOf(value, path, "an object")
264
+ if known(source, node, path, depth) then
265
+ return source
266
+ end
267
+ local before = visits
268
+ enter(source, path, depth)
269
+ if not hasOnlyStringKeys(source) then
270
+ fail(path, "expected an object")
271
+ end
272
+ local _fallback = fallback
273
+ local defaults = if type(_fallback) == "table" then fallback else nil
274
+ local copy = {}
275
+ local declared = {}
276
+ local top = 0
277
+ local same = true
278
+ if discriminant ~= nil then
279
+ local _discriminant = discriminant
280
+ declared[_discriminant] = true
281
+ copy[discriminant] = source[discriminant]
282
+ count(`{path}.{discriminant}`)
283
+ end
284
+ for _, _binding in node.fields do
285
+ local name = _binding[1]
286
+ local child = _binding[2]
287
+ declared[name] = true
288
+ local field = source[name]
289
+ local fieldPath = `{path}.{name}`
290
+ local _fieldDefault = defaults
291
+ if _fieldDefault ~= nil then
292
+ _fieldDefault = _fieldDefault[name]
293
+ end
294
+ local fieldDefault = _fieldDefault
295
+ if field == nil then
296
+ if admitsNil(child) then
297
+ continue
298
+ end
299
+ if fieldDefault == nil then
300
+ fail(fieldPath, "missing; a field added to existing data needs a migration step that fills it")
301
+ end
302
+ copy[name] = fieldDefault
303
+ same = false
304
+ continue
305
+ end
306
+ local result = walk(child, field, fieldDefault, fieldPath, depth + 1)
307
+ top = math.max(top, height)
308
+ if not rawequal(result, field) then
309
+ same = false
310
+ end
311
+ copy[name] = result
312
+ end
313
+ for key, entry in pairs(source) do
314
+ if declared[key] ~= nil then
315
+ continue
316
+ end
317
+ local result = json(entry, `{path}.{key}`, depth + 1)
318
+ top = math.max(top, height)
319
+ if not rawequal(result, entry) then
320
+ same = false
321
+ end
322
+ copy[key] = result
323
+ end
324
+ return finish(source, copy, node, before, top, same)
325
+ end
326
+ function walk(node, value, fallback, path, depth)
327
+ local _exp = node.kind
328
+ repeat
329
+ local _fallthrough = false
330
+ if _exp == "nil" then
331
+ fail(path, "expected nothing")
332
+ _fallthrough = true
333
+ end
334
+ if _fallthrough or _exp == "bool" then
335
+ local _value = value
336
+ if not (type(_value) == "boolean") then
337
+ local _exp_1 = path
338
+ local _value_1 = value
339
+ fail(_exp_1, `expected boolean, got {typeof(_value_1)}`)
340
+ end
341
+ return scalar(value, path)
342
+ end
343
+ if _exp == "string" then
344
+ return scalar(text(value, path), path)
345
+ end
346
+ if _exp == "literal" then
347
+ if value ~= node.value then
348
+ fail(path, `expected {describe(node)}`)
349
+ end
350
+ return scalar(value, path)
351
+ end
352
+ if _exp == "f64" then
353
+ return scalar(number(value, path), path)
354
+ end
355
+ if _exp == "f32" then
356
+ if math.abs(number(value, path)) > F32_MAX then
357
+ fail(path, "out of f32 range")
358
+ end
359
+ return scalar(value, path)
360
+ end
361
+ if _exp == "u8" then
362
+ _fallthrough = true
363
+ end
364
+ if _fallthrough or _exp == "u16" then
365
+ _fallthrough = true
366
+ end
367
+ if _fallthrough or _exp == "u32" then
368
+ local amount = number(value, path)
369
+ local maximum = UINT_MAX[node.kind]
370
+ if amount % 1 ~= 0 or amount < 0 or amount > maximum then
371
+ fail(path, `expected an integer in [0, {maximum}]`)
372
+ end
373
+ return scalar(amount, path)
374
+ end
375
+ if _exp == "object" then
376
+ return object(node, value, fallback, path, depth)
377
+ end
378
+ if _exp == "array" then
379
+ _fallthrough = true
380
+ end
381
+ if _fallthrough or _exp == "tuple" then
382
+ local source = tableOf(value, path, "an array")
383
+ if known(source, node, path, depth) then
384
+ return source
385
+ end
386
+ local before = visits
387
+ enter(source, path, depth)
388
+ local length = denseLength(source)
389
+ if length == nil then
390
+ fail(path, "expected an array")
391
+ end
392
+ if node.kind == "tuple" and length ~= #node.elements then
393
+ fail(path, `expected {#node.elements} elements`)
394
+ end
395
+ local items = source
396
+ local copy = {}
397
+ local top = 0
398
+ local same = true
399
+ do
400
+ local i = 0
401
+ local _shouldIncrement = false
402
+ while true do
403
+ if _shouldIncrement then
404
+ i += 1
405
+ else
406
+ _shouldIncrement = true
407
+ end
408
+ if not (i < length) then
409
+ break
410
+ end
411
+ local element = if node.kind == "tuple" then node.elements[i + 1] else node.element
412
+ local result = walk(element, items[i + 1], nil, `{path}[{i}]`, depth + 1)
413
+ top = math.max(top, height)
414
+ if not rawequal(result, items[i + 1]) then
415
+ same = false
416
+ end
417
+ table.insert(copy, result)
418
+ end
419
+ end
420
+ return finish(source, copy, node, before, top, same)
421
+ end
422
+ if _exp == "map" then
423
+ local source = tableOf(value, path, "an object")
424
+ if known(source, node, path, depth) then
425
+ return source
426
+ end
427
+ local before = visits
428
+ enter(source, path, depth)
429
+ if not hasOnlyStringKeys(source) then
430
+ fail(path, "expected an object with string keys")
431
+ end
432
+ local keys = {}
433
+ local restricted = false
434
+ if node.key.kind == "union" then
435
+ restricted = true
436
+ for _, branch in node.key.branches do
437
+ local _arg0 = tostring(branch.value)
438
+ keys[_arg0] = true
439
+ end
440
+ elseif node.key.kind ~= "string" then
441
+ fail(path, "a saved map has string keys")
442
+ end
443
+ local copy = {}
444
+ local top = 0
445
+ local same = true
446
+ for key, entry in pairs(source) do
447
+ if not (type(key) == "string") then
448
+ fail(path, "expected an object with string keys")
449
+ end
450
+ if restricted and not (keys[key] ~= nil) then
451
+ fail(`{path}.{key}`, "not a key this map declares")
452
+ end
453
+ local result = walk(node.value, entry, nil, `{path}.{key}`, depth + 1)
454
+ top = math.max(top, height)
455
+ if not rawequal(result, entry) then
456
+ same = false
457
+ end
458
+ copy[key] = result
459
+ end
460
+ return finish(source, copy, node, before, top, same)
461
+ end
462
+ if _exp == "union" then
463
+ local _value = value
464
+ local kind = typeof(_value)
465
+ for _, branch in node.branches do
466
+ if branch.kind == "nil" then
467
+ continue
468
+ end
469
+ if branch.kind == "literal" then
470
+ if value == branch.value then
471
+ return scalar(value, path)
472
+ end
473
+ elseif branch.kind == "variant" then
474
+ local _value_1 = value
475
+ local _condition = not (type(_value_1) == "table")
476
+ if not _condition then
477
+ _condition = value[branch.key] ~= branch.value
478
+ end
479
+ if _condition then
480
+ continue
481
+ end
482
+ local _fallback = fallback
483
+ local _condition_1 = type(_fallback) == "table"
484
+ if _condition_1 then
485
+ _condition_1 = fallback[branch.key] == branch.value
486
+ end
487
+ local own = _condition_1
488
+ return object(branch.body, value, if own then fallback else nil, path, depth, branch.key)
489
+ elseif runtimeKind(branch) == kind then
490
+ return walk(branch, value, fallback, path, depth)
491
+ end
492
+ end
493
+ return fail(path, `expected {describe(node)}, got {kind}`)
494
+ end
495
+ return fail(path, `{node.kind} cannot be saved; a save holds JSON`)
496
+ until true
497
+ end
498
+ return walk(root, rootValue, rootDefault, "", 1)
499
+ end
500
+ --[[
501
+ *
502
+ * @internal Rules 14 and 76: validates declared positions, keeps undeclared keys in place, and returns a deeply frozen
503
+ * copy. `defaults` fills a missing required key only along object fields (rule 76). Never throws on bad data, and a
504
+ * failure names the path and the reason, never the value.
505
+
506
+ ]]
507
+ local function admit(node, value, defaults)
508
+ local ok, result = pcall(run, node, value, defaults)
509
+ if ok then
510
+ return {
511
+ ok = true,
512
+ value = result,
513
+ }
514
+ end
515
+ local _binding = result
516
+ local marker = _binding[1]
517
+ local path = _binding[2]
518
+ local reason = _binding[3]
519
+ if marker == FAILURE then
520
+ return {
521
+ ok = false,
522
+ path = path,
523
+ reason = reason,
524
+ }
525
+ end
526
+ error(result)
527
+ end
528
+ --* @internal The schema with its defaults admitted and frozen once (rule 4).
529
+ local function sealSchema(schema)
530
+ local node = schema.node or OPEN
531
+ local result = admit(node, schema.defaults)
532
+ if not result.ok then
533
+ error(`{schema.name}{result.path}: {result.reason}`, 0)
534
+ end
535
+ local _object = table.clone(schema)
536
+ setmetatable(_object, nil)
537
+ _object.node = node
538
+ _object.defaults = result.value
539
+ return _object
540
+ end
541
+ local function isObject(value)
542
+ local _value = value
543
+ local _condition = type(_value) == "table"
544
+ if _condition then
545
+ _condition = hasOnlyStringKeys(value)
546
+ end
547
+ return _condition
548
+ end
549
+ local function copyJson(value)
550
+ local _value = value
551
+ if not (type(_value) == "table") then
552
+ return value
553
+ end
554
+ local result = {}
555
+ for key, entry in pairs(value) do
556
+ result[key] = copyJson(entry)
557
+ end
558
+ return result
559
+ end
560
+ --* Rule 75: objects merge into the stored object, anything else replaces it verbatim.
561
+ local function merge(base, patch)
562
+ if not isObject(base) or not isObject(patch) then
563
+ return patch
564
+ end
565
+ local _object = table.clone(base)
566
+ setmetatable(_object, nil)
567
+ local result = _object
568
+ for key, entry in pairs(patch) do
569
+ result[key] = merge(base[key], entry)
570
+ end
571
+ return result
572
+ end
573
+ --[[
574
+ *
575
+ * @internal Rules 74 to 77: a stored record at version v runs `migrations[v]` to the last step on a private copy, each
576
+ * step merged as a patch, then the descriptor is applied (rule 76). An absent payload takes the defaults and runs no
577
+ * migration (rule 18).
578
+
579
+ ]]
580
+ local function loadRecord(schema, payload, version)
581
+ if payload == nil then
582
+ return {
583
+ kind = "ok",
584
+ value = schema.defaults,
585
+ migrated = false,
586
+ }
587
+ end
588
+ if version > schema.version then
589
+ return {
590
+ kind = "newer",
591
+ version = version,
592
+ }
593
+ end
594
+ local current = payload
595
+ local migrated = false
596
+ local steps = schema.migrations or {}
597
+ do
598
+ local index = version
599
+ local _shouldIncrement = false
600
+ while true do
601
+ if _shouldIncrement then
602
+ index += 1
603
+ else
604
+ _shouldIncrement = true
605
+ end
606
+ if not (index < schema.version) then
607
+ break
608
+ end
609
+ local step = steps[index + 1]
610
+ if step == nil then
611
+ continue
612
+ end
613
+ local status, patch = call(step, copyJson(current), nil)
614
+ local label = `{schema.name}: migrations[{index}]`
615
+ if status == 1 then
616
+ return {
617
+ kind = "unreadable",
618
+ code = "migration",
619
+ reason = `{label} threw`,
620
+ }
621
+ end
622
+ if status == 2 then
623
+ return {
624
+ kind = "unreadable",
625
+ code = "migration",
626
+ reason = `{label} yielded`,
627
+ }
628
+ end
629
+ if not (type(patch) == "table") or not isObject(patch) then
630
+ return {
631
+ kind = "unreadable",
632
+ code = "migration",
633
+ reason = `{label} returned {typeof(patch)}, not a record`,
634
+ }
635
+ end
636
+ current = merge(current, patch)
637
+ migrated = true
638
+ end
639
+ end
640
+ local result = admit(schema.node or OPEN, current, schema.defaults)
641
+ if not result.ok then
642
+ return {
643
+ kind = "unreadable",
644
+ code = "admission",
645
+ reason = `{schema.name}{result.path}: {result.reason}{if migrated then ` (after migrating from version {version})` else ""}`,
646
+ }
647
+ end
648
+ return {
649
+ kind = "ok",
650
+ value = result.value,
651
+ migrated = migrated,
652
+ }
653
+ end
654
+ --* @internal Rule 14's walk over a callback's result, without defaults. Throws naming the store and path, never the value.
655
+ local function admitWrite(schema, value)
656
+ local _value = value
657
+ if not (type(_value) == "table") then
658
+ local _exp = schema.name
659
+ local _value_1 = value
660
+ error(`{_exp}: a store's value is a record, got {typeof(_value_1)}`, 0)
661
+ end
662
+ local result = admit(schema.node or OPEN, value)
663
+ if not result.ok then
664
+ error(`{schema.name}{result.path}: {result.reason}`, 0)
665
+ end
666
+ return result.value
667
+ end
668
+ return {
669
+ admit = admit,
670
+ sealSchema = sealSchema,
671
+ loadRecord = loadRecord,
672
+ admitWrite = admitWrite,
673
+ }
@@ -0,0 +1 @@
1
+ export {};