@trioforge/crucible 0.0.0-stage → 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.
- package/LICENSE +21 -0
- package/client/package.json +4 -0
- package/emit/package.json +4 -0
- package/out/all.d.ts +7 -0
- package/out/all.luau +2 -0
- package/out/attribute.d.ts +7 -0
- package/out/attribute.luau +7 -0
- package/out/client/index.d.ts +10 -0
- package/out/client/init.luau +55 -0
- package/out/configs.d.ts +37 -0
- package/out/configs.luau +46 -0
- package/out/crucible-behaviour.d.ts +16 -0
- package/out/crucible-behaviour.luau +78 -0
- package/out/crucible-object.d.ts +10 -0
- package/out/crucible-object.luau +69 -0
- package/out/data.d.ts +224 -0
- package/out/data.luau +97 -0
- package/out/emit/configs.d.ts +4 -0
- package/out/emit/configs.luau +22 -0
- package/out/emit/data.d.ts +6 -0
- package/out/emit/data.luau +19 -0
- package/out/emit/index.d.ts +45 -0
- package/out/emit/init.luau +101 -0
- package/out/emit/network.d.ts +6 -0
- package/out/emit/network.luau +36 -0
- package/out/emit/replicated.d.ts +4 -0
- package/out/emit/replicated.luau +13 -0
- package/out/index.d.ts +20 -0
- package/out/init.luau +34 -0
- package/out/inject.d.ts +1 -0
- package/out/inject.luau +7 -0
- package/out/internal/binding.d.ts +1 -0
- package/out/internal/binding.luau +615 -0
- package/out/internal/boot.d.ts +1 -0
- package/out/internal/boot.luau +562 -0
- package/out/internal/configs/limits.d.ts +10 -0
- package/out/internal/configs/limits.luau +25 -0
- package/out/internal/configs/port.d.ts +29 -0
- package/out/internal/configs/port.luau +99 -0
- package/out/internal/configs/runtime.d.ts +21 -0
- package/out/internal/configs/runtime.luau +981 -0
- package/out/internal/construction-claim.d.ts +1 -0
- package/out/internal/construction-claim.luau +26 -0
- package/out/internal/data/admission.d.ts +1 -0
- package/out/internal/data/admission.luau +673 -0
- package/out/internal/data/declarations.d.ts +1 -0
- package/out/internal/data/declarations.luau +384 -0
- package/out/internal/data/envelope.d.ts +1 -0
- package/out/internal/data/envelope.luau +318 -0
- package/out/internal/data/handles.d.ts +1 -0
- package/out/internal/data/handles.luau +255 -0
- package/out/internal/data/inbox-transforms.d.ts +1 -0
- package/out/internal/data/inbox-transforms.luau +458 -0
- package/out/internal/data/inbox.d.ts +1 -0
- package/out/internal/data/inbox.luau +437 -0
- package/out/internal/data/lease.d.ts +1 -0
- package/out/internal/data/lease.luau +304 -0
- package/out/internal/data/limits.d.ts +1 -0
- package/out/internal/data/limits.luau +234 -0
- package/out/internal/data/messages.d.ts +1 -0
- package/out/internal/data/messages.luau +69 -0
- package/out/internal/data/operation-id.d.ts +1 -0
- package/out/internal/data/operation-id.luau +61 -0
- package/out/internal/data/paths.d.ts +2 -0
- package/out/internal/data/paths.luau +323 -0
- package/out/internal/data/port.d.ts +1 -0
- package/out/internal/data/port.luau +290 -0
- package/out/internal/data/profiles.d.ts +1 -0
- package/out/internal/data/profiles.luau +862 -0
- package/out/internal/data/receipts.d.ts +1 -0
- package/out/internal/data/receipts.luau +190 -0
- package/out/internal/data/record-bindings.d.ts +1 -0
- package/out/internal/data/record-bindings.luau +92 -0
- package/out/internal/data/record-declarations.d.ts +1 -0
- package/out/internal/data/record-declarations.luau +140 -0
- package/out/internal/data/records.d.ts +1 -0
- package/out/internal/data/records.luau +400 -0
- package/out/internal/data/runtime.d.ts +1 -0
- package/out/internal/data/runtime.luau +918 -0
- package/out/internal/data/scheduler.d.ts +1 -0
- package/out/internal/data/scheduler.luau +507 -0
- package/out/internal/data/session.d.ts +1 -0
- package/out/internal/data/session.luau +234 -0
- package/out/internal/data/shutdown.d.ts +1 -0
- package/out/internal/data/shutdown.luau +91 -0
- package/out/internal/data/waiters.d.ts +1 -0
- package/out/internal/data/waiters.luau +65 -0
- package/out/internal/dense-list.d.ts +1 -0
- package/out/internal/dense-list.luau +155 -0
- package/out/internal/diagnostics.d.ts +1 -0
- package/out/internal/diagnostics.luau +29 -0
- package/out/internal/hooks.d.ts +1 -0
- package/out/internal/hooks.luau +53 -0
- package/out/internal/implementers.d.ts +1 -0
- package/out/internal/implementers.luau +119 -0
- package/out/internal/lifecycle.d.ts +1 -0
- package/out/internal/lifecycle.luau +912 -0
- package/out/internal/log-output.d.ts +1 -0
- package/out/internal/log-output.luau +106 -0
- package/out/internal/network/catalog.d.ts +26 -0
- package/out/internal/network/catalog.luau +182 -0
- package/out/internal/network/claims.d.ts +1 -0
- package/out/internal/network/claims.luau +154 -0
- package/out/internal/network/client-session.d.ts +1 -0
- package/out/internal/network/client-session.luau +403 -0
- package/out/internal/network/codec.d.ts +63 -0
- package/out/internal/network/codec.luau +1183 -0
- package/out/internal/network/envelope.d.ts +1 -0
- package/out/internal/network/envelope.luau +247 -0
- package/out/internal/network/limits.d.ts +26 -0
- package/out/internal/network/limits.luau +55 -0
- package/out/internal/network/port.d.ts +1 -0
- package/out/internal/network/port.luau +19 -0
- package/out/internal/network/roblox-network-port.d.ts +1 -0
- package/out/internal/network/roblox-network-port.luau +105 -0
- package/out/internal/network/schema.d.ts +72 -0
- package/out/internal/network/schema.luau +386 -0
- package/out/internal/network/server-session.d.ts +1 -0
- package/out/internal/network/server-session.luau +393 -0
- package/out/internal/network/session.d.ts +1 -0
- package/out/internal/network/session.luau +266 -0
- package/out/internal/port.d.ts +1 -0
- package/out/internal/port.luau +12 -0
- package/out/internal/reactive/context.d.ts +1 -0
- package/out/internal/reactive/context.luau +149 -0
- package/out/internal/reactive/flush.d.ts +1 -0
- package/out/internal/reactive/flush.luau +355 -0
- package/out/internal/reactive/graph.d.ts +1 -0
- package/out/internal/reactive/graph.luau +626 -0
- package/out/internal/reactive/limits.d.ts +1 -0
- package/out/internal/reactive/limits.luau +54 -0
- package/out/internal/reactive/ownership.d.ts +1 -0
- package/out/internal/reactive/ownership.luau +626 -0
- package/out/internal/registry.d.ts +1 -0
- package/out/internal/registry.luau +455 -0
- package/out/internal/roblox-port.d.ts +1 -0
- package/out/internal/roblox-port.luau +117 -0
- package/out/internal/runner.d.ts +1 -0
- package/out/internal/runner.luau +86 -0
- package/out/internal/scheduler.d.ts +1 -0
- package/out/internal/scheduler.luau +207 -0
- package/out/internal/state.d.ts +1 -0
- package/out/internal/state.luau +41 -0
- package/out/internal/sync/client-sync.d.ts +1 -0
- package/out/internal/sync/client-sync.luau +157 -0
- package/out/internal/sync/declarations.d.ts +1 -0
- package/out/internal/sync/declarations.luau +276 -0
- package/out/internal/sync/frame.d.ts +1 -0
- package/out/internal/sync/frame.luau +91 -0
- package/out/internal/sync/server-sync.d.ts +1 -0
- package/out/internal/sync/server-sync.luau +502 -0
- package/out/internal/sync/values.d.ts +1 -0
- package/out/internal/sync/values.luau +930 -0
- package/out/log-level.d.ts +7 -0
- package/out/log-level.luau +21 -0
- package/out/logging.d.ts +34 -0
- package/out/logging.luau +233 -0
- package/out/network.d.ts +66 -0
- package/out/network.luau +24 -0
- package/out/reactive.d.ts +76 -0
- package/out/reactive.luau +136 -0
- package/out/replicated.d.ts +38 -0
- package/out/replicated.luau +45 -0
- package/out/server/data.d.ts +54 -0
- package/out/server/data.luau +44 -0
- package/out/server/index.d.ts +18 -0
- package/out/server/init.luau +62 -0
- package/out/server/publish.d.ts +20 -0
- package/out/server/publish.luau +24 -0
- package/out/subsystem.d.ts +4 -0
- package/out/subsystem.luau +30 -0
- package/out/vide/index.d.ts +7 -0
- package/out/vide/init.luau +50 -0
- package/package.json +52 -5
- package/server/package.json +4 -0
- package/transformer/data.cjs +72 -0
- package/transformer/declarations.cjs +153 -0
- package/transformer/diagnostics.cjs +19 -0
- package/transformer/index.js +741 -0
- package/transformer/network.cjs +961 -0
- package/transformer/type-id.cjs +80 -0
- package/vide/package.json +4 -0
- package/README.md +0 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local claims = {}
|
|
3
|
+
--* @internal
|
|
4
|
+
local function setClaim(state)
|
|
5
|
+
local _arg0 = coroutine.running()
|
|
6
|
+
local _state = state
|
|
7
|
+
claims[_arg0] = _state
|
|
8
|
+
end
|
|
9
|
+
--* @internal
|
|
10
|
+
local function readClaim()
|
|
11
|
+
local _arg0 = coroutine.running()
|
|
12
|
+
return claims[_arg0]
|
|
13
|
+
end
|
|
14
|
+
--* @internal
|
|
15
|
+
local function clearClaim(thread)
|
|
16
|
+
if thread == nil then
|
|
17
|
+
thread = coroutine.running()
|
|
18
|
+
end
|
|
19
|
+
local _thread = thread
|
|
20
|
+
claims[_thread] = nil
|
|
21
|
+
end
|
|
22
|
+
return {
|
|
23
|
+
setClaim = setClaim,
|
|
24
|
+
readClaim = readClaim,
|
|
25
|
+
clearClaim = clearClaim,
|
|
26
|
+
}
|
|
@@ -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 {};
|