@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.
- package/maths/spline/package.json +4 -0
- package/out/client/index.d.ts +7 -0
- package/out/client/init.luau +36 -1
- package/out/configs.d.ts +37 -0
- package/out/configs.luau +46 -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 +5 -1
- package/out/emit/init.luau +23 -7
- 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 +8 -0
- package/out/init.luau +23 -0
- package/out/internal/binding.luau +5 -0
- package/out/internal/boot.luau +112 -8
- 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/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/lifecycle.luau +45 -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 +146 -0
- package/out/internal/network/client-session.d.ts +1 -0
- package/out/internal/network/client-session.luau +405 -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 +395 -0
- package/out/internal/network/session.d.ts +1 -0
- package/out/internal/network/session.luau +266 -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.luau +10 -1
- 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/maths/spline/curve.d.ts +35 -0
- package/out/maths/spline/curve.luau +1122 -0
- package/out/maths/spline/index.d.ts +2 -0
- package/out/maths/spline/init.luau +8 -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 +17 -0
- package/out/server/init.luau +58 -4
- package/out/server/publish.d.ts +20 -0
- package/out/server/publish.luau +24 -0
- package/out/testing/index.d.ts +38 -0
- package/out/testing/init.luau +189 -0
- package/out/util/random/index.d.ts +24 -0
- package/out/util/random/init.luau +301 -0
- package/out/vide/index.d.ts +7 -0
- package/out/vide/init.luau +50 -0
- package/package.json +28 -2
- package/testing/package.json +4 -0
- package/transformer/data.cjs +72 -0
- package/transformer/declarations.cjs +153 -0
- package/transformer/index.js +87 -23
- package/transformer/network.cjs +961 -0
- package/util/random/package.json +4 -0
- package/vide/package.json +4 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local _schema = TS.import(script, script.Parent.Parent, "network", "schema")
|
|
4
|
+
local assertDescriptor = _schema.assertDescriptor
|
|
5
|
+
local n = _schema.n
|
|
6
|
+
local readState = TS.import(script, script.Parent.Parent, "reactive", "graph").readState
|
|
7
|
+
local MAX_REPLICATED_DECLARATIONS = TS.import(script, script.Parent.Parent, "reactive", "limits").MAX_REPLICATED_DECLARATIONS
|
|
8
|
+
local _declarations = TS.import(script, script.Parent.Parent, "sync", "declarations")
|
|
9
|
+
local declarationOf = _declarations.declarationOf
|
|
10
|
+
local declareFrameworkReplicated = _declarations.declareFrameworkReplicated
|
|
11
|
+
local replicatedCapacity = _declarations.replicatedCapacity
|
|
12
|
+
local _values = TS.import(script, script.Parent.Parent, "sync", "values")
|
|
13
|
+
local admitReplicated = _values.admit
|
|
14
|
+
local compilePlan = _values.compilePlan
|
|
15
|
+
local sealSchema = TS.import(script, script.Parent, "admission").sealSchema
|
|
16
|
+
local rootHandle = TS.import(script, script.Parent, "handles").rootHandle
|
|
17
|
+
local _limits = TS.import(script, script.Parent, "limits")
|
|
18
|
+
local LIVE_NAME_PREFIX = _limits.LIVE_NAME_PREFIX
|
|
19
|
+
local MAX_DATA_DECLARATIONS = _limits.MAX_DATA_DECLARATIONS
|
|
20
|
+
local MAX_PROFILE_STORES = _limits.MAX_PROFILE_STORES
|
|
21
|
+
local VIEW_NAME_MAX_BYTES = _limits.VIEW_NAME_MAX_BYTES
|
|
22
|
+
local VIEW_NAME_PREFIX = _limits.VIEW_NAME_PREFIX
|
|
23
|
+
local _paths = TS.import(script, script.Parent, "paths")
|
|
24
|
+
local fieldPaths = _paths.fieldPaths
|
|
25
|
+
local getPath = _paths.getPath
|
|
26
|
+
local pathLess = _paths.pathLess
|
|
27
|
+
local samePath = _paths.samePath
|
|
28
|
+
local showPath = _paths.showPath
|
|
29
|
+
local _record_declarations = TS.import(script, script.Parent, "record-declarations")
|
|
30
|
+
local checkStoreName = _record_declarations.checkStoreName
|
|
31
|
+
local dataDeclarationCount = _record_declarations.dataDeclarationCount
|
|
32
|
+
local dataRegistrySealed = _record_declarations.dataRegistrySealed
|
|
33
|
+
local registerProfileName = _record_declarations.registerProfileName
|
|
34
|
+
local storeNameTaken = _record_declarations.storeNameTaken
|
|
35
|
+
--* @internal One top-level field the owner's client receives, and its view declaration (rules 125, 134).
|
|
36
|
+
--* @internal A profile store as both realms know it, built once at module evaluation (rule 4).
|
|
37
|
+
--* @internal What the server entry installs so a shared declaration reaches the durability engine (rule 115).
|
|
38
|
+
--[[
|
|
39
|
+
*
|
|
40
|
+
* The record a store carries in its metatable. The key is a string, not a private table, so `replicatedPaths` reads a
|
|
41
|
+
* declaration made by another module set (rule 131).
|
|
42
|
+
|
|
43
|
+
]]
|
|
44
|
+
local RECORD = "__crucibleProfileStore"
|
|
45
|
+
local profiles = {}
|
|
46
|
+
local clientRoots = {}
|
|
47
|
+
local serverSide
|
|
48
|
+
local markMeta = {}
|
|
49
|
+
--* @internal `serverOnly(value)`: a frozen mark recognized by its private metatable (rule 129).
|
|
50
|
+
local function markServerOnly(value)
|
|
51
|
+
return table.freeze(setmetatable({
|
|
52
|
+
value = value,
|
|
53
|
+
}, markMeta))
|
|
54
|
+
end
|
|
55
|
+
local function isMark(value)
|
|
56
|
+
local _value = value
|
|
57
|
+
local _condition = type(_value) == "table"
|
|
58
|
+
if _condition then
|
|
59
|
+
_condition = getmetatable(value) == markMeta
|
|
60
|
+
end
|
|
61
|
+
return _condition
|
|
62
|
+
end
|
|
63
|
+
--* Replaces every mark by the value it wraps, recording where each one sat as an array of keys (rule 4).
|
|
64
|
+
local function unwrap(value, path, found)
|
|
65
|
+
if isMark(value) then
|
|
66
|
+
local _found = found
|
|
67
|
+
local _path = path
|
|
68
|
+
table.insert(_found, _path)
|
|
69
|
+
return unwrap(value.value, path, found)
|
|
70
|
+
end
|
|
71
|
+
local _value = value
|
|
72
|
+
local _condition = not (type(_value) == "table")
|
|
73
|
+
if not _condition then
|
|
74
|
+
_condition = getmetatable(value) ~= nil
|
|
75
|
+
end
|
|
76
|
+
if _condition then
|
|
77
|
+
return value
|
|
78
|
+
end
|
|
79
|
+
local copy
|
|
80
|
+
for key, entry in pairs(value) do
|
|
81
|
+
local _array = {}
|
|
82
|
+
local _length = #_array
|
|
83
|
+
local _pathLength = #path
|
|
84
|
+
table.move(path, 1, _pathLength, _length + 1, _array)
|
|
85
|
+
_length += _pathLength
|
|
86
|
+
_array[_length + 1] = tostring(key)
|
|
87
|
+
local inner = unwrap(entry, _array, found)
|
|
88
|
+
if rawequal(inner, entry) then
|
|
89
|
+
continue
|
|
90
|
+
end
|
|
91
|
+
if copy == nil then
|
|
92
|
+
copy = table.clone(value)
|
|
93
|
+
end
|
|
94
|
+
copy[key] = inner
|
|
95
|
+
end
|
|
96
|
+
return copy or value
|
|
97
|
+
end
|
|
98
|
+
local function checkMarks(name, found, expected)
|
|
99
|
+
local _array = {}
|
|
100
|
+
local _length = #_array
|
|
101
|
+
table.move(found, 1, #found, _length + 1, _array)
|
|
102
|
+
table.sort(_array, pathLess)
|
|
103
|
+
local marked = _array
|
|
104
|
+
local _array_1 = {}
|
|
105
|
+
local _length_1 = #_array_1
|
|
106
|
+
table.move(expected, 1, #expected, _length_1 + 1, _array_1)
|
|
107
|
+
table.sort(_array_1, pathLess)
|
|
108
|
+
local typed = _array_1
|
|
109
|
+
for _, path in marked do
|
|
110
|
+
-- ▼ ReadonlyArray.some ▼
|
|
111
|
+
local _result = false
|
|
112
|
+
local _callback = function(other)
|
|
113
|
+
return samePath(other, path)
|
|
114
|
+
end
|
|
115
|
+
for _k, _v in typed do
|
|
116
|
+
if _callback(_v, _k - 1, typed) then
|
|
117
|
+
_result = true
|
|
118
|
+
break
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
-- ▲ ReadonlyArray.some ▲
|
|
122
|
+
if not _result then
|
|
123
|
+
error(`{name} {showPath(path)} is marked serverOnly in its template value but not in its type`, 0)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
for _, path in typed do
|
|
127
|
+
-- ▼ ReadonlyArray.some ▼
|
|
128
|
+
local _result = false
|
|
129
|
+
local _callback = function(other)
|
|
130
|
+
return samePath(other, path)
|
|
131
|
+
end
|
|
132
|
+
for _k, _v in marked do
|
|
133
|
+
if _callback(_v, _k - 1, marked) then
|
|
134
|
+
_result = true
|
|
135
|
+
break
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
-- ▲ ReadonlyArray.some ▲
|
|
139
|
+
if not _result then
|
|
140
|
+
error(`{name} {showPath(path)} is serverOnly in its type but its template value is not marked`, 0)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
--* A field view's descriptor: the field's replica node admitting undefined (rule 125).
|
|
145
|
+
local function admittingUndefined(node)
|
|
146
|
+
if node.kind == "union" then
|
|
147
|
+
for _, branch in node.branches do
|
|
148
|
+
if branch.kind == "nil" then
|
|
149
|
+
return node
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
local _array = { n["nil"] }
|
|
153
|
+
local _length = #_array
|
|
154
|
+
local _array_1 = node.branches
|
|
155
|
+
table.move(_array_1, 1, #_array_1, _length + 1, _array)
|
|
156
|
+
return n.union(_array)
|
|
157
|
+
end
|
|
158
|
+
return n.union({ n["nil"], node })
|
|
159
|
+
end
|
|
160
|
+
--* Rules 2 and 134: every view name is checked, and the realm's room counted, before any is declared.
|
|
161
|
+
local function declareViews(name, replica)
|
|
162
|
+
local prefix = `{VIEW_NAME_PREFIX}{name}.`
|
|
163
|
+
for _, _binding in replica.fields do
|
|
164
|
+
local field = _binding[1]
|
|
165
|
+
if (string.match(field, "^[A-Za-z][A-Za-z0-9_]*$")) == nil then
|
|
166
|
+
error(`{name}["{field}"]: a field the owner's client receives is named by one segment, [A-Za-z][A-Za-z0-9_]*; rename it or mark it serverOnly`, 0)
|
|
167
|
+
end
|
|
168
|
+
local view = prefix .. field
|
|
169
|
+
if #view > VIEW_NAME_MAX_BYTES then
|
|
170
|
+
error(`{name}.{field}: its view name {view} is {#view} bytes, over {VIEW_NAME_MAX_BYTES}; this store allows field names of at most {VIEW_NAME_MAX_BYTES - #prefix} bytes`, 0)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
local needed = 1 + #replica.fields
|
|
174
|
+
local room = replicatedCapacity()
|
|
175
|
+
if room < needed then
|
|
176
|
+
error(`{name} needs {needed} replicated declarations; {MAX_REPLICATED_DECLARATIONS - room} of {MAX_REPLICATED_DECLARATIONS} are in use`, 0)
|
|
177
|
+
end
|
|
178
|
+
local live = declareFrameworkReplicated(`{LIVE_NAME_PREFIX}{name}`, false, n.bool)
|
|
179
|
+
local _exp = replica.fields
|
|
180
|
+
-- ▼ ReadonlyArray.map ▼
|
|
181
|
+
local _newValue = table.create(#_exp)
|
|
182
|
+
local _callback = function(_param)
|
|
183
|
+
local field = _param[1]
|
|
184
|
+
local node = _param[2]
|
|
185
|
+
local handle = declareFrameworkReplicated(prefix .. field, nil, admittingUndefined(node))
|
|
186
|
+
return {
|
|
187
|
+
name = field,
|
|
188
|
+
handle = handle,
|
|
189
|
+
declaration = declarationOf(handle),
|
|
190
|
+
}
|
|
191
|
+
end
|
|
192
|
+
for _k, _v in _exp do
|
|
193
|
+
_newValue[_k] = _callback(_v, _k - 1, _exp)
|
|
194
|
+
end
|
|
195
|
+
-- ▲ ReadonlyArray.map ▲
|
|
196
|
+
local fields = _newValue
|
|
197
|
+
return {
|
|
198
|
+
live = live,
|
|
199
|
+
fields = fields,
|
|
200
|
+
}
|
|
201
|
+
end
|
|
202
|
+
local function clientSource(declaration)
|
|
203
|
+
local byName = {}
|
|
204
|
+
for _, field in declaration.fields do
|
|
205
|
+
local _name = field.name
|
|
206
|
+
byName[_name] = field
|
|
207
|
+
end
|
|
208
|
+
return {
|
|
209
|
+
name = declaration.name,
|
|
210
|
+
client = true,
|
|
211
|
+
beforeRead = function(self) end,
|
|
212
|
+
write = function(self)
|
|
213
|
+
error(`{declaration.name}() is read-only on the client`, 3)
|
|
214
|
+
end,
|
|
215
|
+
read = function(self, path)
|
|
216
|
+
local live = readState(declaration.liveDeclaration.node) == true
|
|
217
|
+
if #path == 0 then
|
|
218
|
+
if not live then
|
|
219
|
+
return declaration.clientTemplate
|
|
220
|
+
end
|
|
221
|
+
local root = {}
|
|
222
|
+
for _, field in declaration.fields do
|
|
223
|
+
root[field.name] = readState(field.declaration.node)
|
|
224
|
+
end
|
|
225
|
+
return table.freeze(root)
|
|
226
|
+
end
|
|
227
|
+
local _arg0 = path[1]
|
|
228
|
+
local field = byName[_arg0]
|
|
229
|
+
if live and field ~= nil then
|
|
230
|
+
return getPath(readState(field.declaration.node), path, 1)
|
|
231
|
+
end
|
|
232
|
+
return getPath(declaration.clientTemplate, path)
|
|
233
|
+
end,
|
|
234
|
+
}
|
|
235
|
+
end
|
|
236
|
+
--* @internal Rule 124: the local player's root handle, made once per realm.
|
|
237
|
+
local function clientRoot(declaration)
|
|
238
|
+
local _declaration = declaration
|
|
239
|
+
local root = clientRoots[_declaration]
|
|
240
|
+
if root == nil then
|
|
241
|
+
root = rootHandle(clientSource(declaration), declaration.replica)
|
|
242
|
+
local _declaration_1 = declaration
|
|
243
|
+
local _root = root
|
|
244
|
+
clientRoots[_declaration_1] = _root
|
|
245
|
+
end
|
|
246
|
+
return root
|
|
247
|
+
end
|
|
248
|
+
--* @internal Rule 126: tracked; the lifecycle is ready and true, and every field view is ready.
|
|
249
|
+
local function clientLoaded(declaration)
|
|
250
|
+
local live = declaration.liveDeclaration
|
|
251
|
+
if readState(live.status) ~= "ready" or readState(live.node) ~= true then
|
|
252
|
+
return false
|
|
253
|
+
end
|
|
254
|
+
for _, field in declaration.fields do
|
|
255
|
+
if readState(field.declaration.status) ~= "ready" then
|
|
256
|
+
return false
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
return true
|
|
260
|
+
end
|
|
261
|
+
local function storeMeta(declaration)
|
|
262
|
+
local name = declaration.name
|
|
263
|
+
local meta = {
|
|
264
|
+
__call = function(_, ...)
|
|
265
|
+
local args = { ... }
|
|
266
|
+
local player = args[1]
|
|
267
|
+
if serverSide ~= nil then
|
|
268
|
+
if player == nil then
|
|
269
|
+
error(`{name} is per player on the server; call {name}(player)`, 2)
|
|
270
|
+
end
|
|
271
|
+
return serverSide.root(declaration, player)
|
|
272
|
+
end
|
|
273
|
+
if #args > 0 then
|
|
274
|
+
error(`a client reads only its own profile; call {name}()`, 2)
|
|
275
|
+
end
|
|
276
|
+
return clientRoot(declaration)
|
|
277
|
+
end,
|
|
278
|
+
__index = function(_, key)
|
|
279
|
+
if serverSide ~= nil then
|
|
280
|
+
return serverSide.member(declaration, key)
|
|
281
|
+
end
|
|
282
|
+
if key == "loaded" then
|
|
283
|
+
return function(_store, player)
|
|
284
|
+
if player ~= nil then
|
|
285
|
+
error(`{name}.loaded(player) is server-only; the client reads {name}.loaded()`, 2)
|
|
286
|
+
end
|
|
287
|
+
return clientLoaded(declaration)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
return function()
|
|
291
|
+
return error(`{name}.{tostring(key)} is server-only`, 2)
|
|
292
|
+
end
|
|
293
|
+
end,
|
|
294
|
+
__newindex = function()
|
|
295
|
+
return error(`{name} is a profile store; it has no fields to assign`, 2)
|
|
296
|
+
end,
|
|
297
|
+
}
|
|
298
|
+
meta[RECORD] = declaration
|
|
299
|
+
return meta
|
|
300
|
+
end
|
|
301
|
+
--[[
|
|
302
|
+
*
|
|
303
|
+
* @internal The emitted `profileStore` (rules 1 to 4, 129, 131, 134). Shared-safe: it touches no service and starts
|
|
304
|
+
* nothing, and every check runs before anything registers.
|
|
305
|
+
|
|
306
|
+
]]
|
|
307
|
+
local function declareProfileStore(name, template, migrations, persist, replica, serverOnlyPaths)
|
|
308
|
+
checkStoreName(name)
|
|
309
|
+
if dataRegistrySealed() then
|
|
310
|
+
error(`{name} was declared after the data registry sealed; declare stores in modules that boot loads`, 3)
|
|
311
|
+
end
|
|
312
|
+
if storeNameTaken(name) then
|
|
313
|
+
error(`{name} is already declared`, 3)
|
|
314
|
+
end
|
|
315
|
+
if #profiles >= MAX_PROFILE_STORES then
|
|
316
|
+
error(`{name}: more than {MAX_PROFILE_STORES} profile stores`, 3)
|
|
317
|
+
end
|
|
318
|
+
if dataDeclarationCount() >= MAX_DATA_DECLARATIONS then
|
|
319
|
+
error(`{name}: more than {MAX_DATA_DECLARATIONS} data declarations`, 3)
|
|
320
|
+
end
|
|
321
|
+
assertDescriptor(persist)
|
|
322
|
+
assertDescriptor(replica)
|
|
323
|
+
if replica.kind ~= "object" then
|
|
324
|
+
error(`{name}: a profile store's value is an object with declared fields`, 3)
|
|
325
|
+
end
|
|
326
|
+
local found = {}
|
|
327
|
+
local unwrapped = unwrap(template, {}, found)
|
|
328
|
+
checkMarks(name, found, serverOnlyPaths)
|
|
329
|
+
local _array = {}
|
|
330
|
+
local _length = #_array
|
|
331
|
+
local _array_1 = (migrations or {})
|
|
332
|
+
table.move(_array_1, 1, #_array_1, _length + 1, _array)
|
|
333
|
+
local steps = _array
|
|
334
|
+
local schema = sealSchema({
|
|
335
|
+
name = name,
|
|
336
|
+
version = #steps,
|
|
337
|
+
defaults = unwrapped,
|
|
338
|
+
node = persist,
|
|
339
|
+
migrations = steps,
|
|
340
|
+
})
|
|
341
|
+
local clientTemplate = admitReplicated(compilePlan(replica), name, schema.defaults)
|
|
342
|
+
local views = declareViews(name, replica)
|
|
343
|
+
local declaration = {
|
|
344
|
+
name = name,
|
|
345
|
+
persist = persist,
|
|
346
|
+
replica = replica,
|
|
347
|
+
schema = schema,
|
|
348
|
+
clientTemplate = clientTemplate,
|
|
349
|
+
live = views.live,
|
|
350
|
+
liveDeclaration = declarationOf(views.live),
|
|
351
|
+
fields = views.fields,
|
|
352
|
+
paths = fieldPaths(replica),
|
|
353
|
+
}
|
|
354
|
+
registerProfileName(name)
|
|
355
|
+
table.insert(profiles, declaration)
|
|
356
|
+
return table.freeze(setmetatable({}, storeMeta(declaration)))
|
|
357
|
+
end
|
|
358
|
+
--* @internal Rule 131: the replica's paths, read from the store's own metatable, never a registry.
|
|
359
|
+
local function replicatedPathsOf(store)
|
|
360
|
+
local _store = store
|
|
361
|
+
local meta = if type(_store) == "table" then (getmetatable(store)) else nil
|
|
362
|
+
local declaration = if type(meta) == "table" then (meta[RECORD]) else nil
|
|
363
|
+
if declaration == nil then
|
|
364
|
+
error("replicatedPaths takes a profile store", 2)
|
|
365
|
+
end
|
|
366
|
+
return declaration.paths
|
|
367
|
+
end
|
|
368
|
+
--* @internal This realm's profile stores, in registration order; DataRuntime binds each when it constructs (104).
|
|
369
|
+
local function profileDeclarations()
|
|
370
|
+
return profiles
|
|
371
|
+
end
|
|
372
|
+
--* @internal The server entry installs its side once per realm, when its module loads (rule 115).
|
|
373
|
+
local function installServerSide(side)
|
|
374
|
+
serverSide = side
|
|
375
|
+
end
|
|
376
|
+
return {
|
|
377
|
+
markServerOnly = markServerOnly,
|
|
378
|
+
clientRoot = clientRoot,
|
|
379
|
+
clientLoaded = clientLoaded,
|
|
380
|
+
declareProfileStore = declareProfileStore,
|
|
381
|
+
replicatedPathsOf = replicatedPathsOf,
|
|
382
|
+
profileDeclarations = profileDeclarations,
|
|
383
|
+
installServerSide = installServerSide,
|
|
384
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local _limits = TS.import(script, script.Parent, "limits")
|
|
4
|
+
local INBOX_MAX_MESSAGES = _limits.INBOX_MAX_MESSAGES
|
|
5
|
+
local MAX_PURCHASE_IDS = _limits.MAX_PURCHASE_IDS
|
|
6
|
+
local OPERATION_ID_MAX_BYTES = _limits.OPERATION_ID_MAX_BYTES
|
|
7
|
+
local OPERATION_MARKERS_PER_KEY = _limits.OPERATION_MARKERS_PER_KEY
|
|
8
|
+
local PURCHASE_ID_MAX_BYTES = _limits.PURCHASE_ID_MAX_BYTES
|
|
9
|
+
local QUARANTINE_MAX_MESSAGES = _limits.QUARANTINE_MAX_MESSAGES
|
|
10
|
+
local SEEN_MAX_MESSAGES = _limits.SEEN_MAX_MESSAGES
|
|
11
|
+
--* @internal
|
|
12
|
+
--* @internal The last owned operation that committed (rule 28).
|
|
13
|
+
--* @internal
|
|
14
|
+
--* @internal
|
|
15
|
+
--* @internal
|
|
16
|
+
--* @internal One JSON value per key (rule 38). `payload` is the stored record exactly; bookkeeping is never inside it.
|
|
17
|
+
--* @internal
|
|
18
|
+
local UNREADABLE = {}
|
|
19
|
+
local function fail(reason)
|
|
20
|
+
error({ UNREADABLE, reason })
|
|
21
|
+
end
|
|
22
|
+
local function counter(value, name)
|
|
23
|
+
local _value = value
|
|
24
|
+
local _condition = not (type(_value) == "number")
|
|
25
|
+
if not _condition then
|
|
26
|
+
_condition = value < 0 or value % 1 ~= 0
|
|
27
|
+
end
|
|
28
|
+
if _condition then
|
|
29
|
+
fail(`bad {name}`)
|
|
30
|
+
end
|
|
31
|
+
return value
|
|
32
|
+
end
|
|
33
|
+
local function time(value, name)
|
|
34
|
+
local _value = value
|
|
35
|
+
local _condition = not (type(_value) == "number")
|
|
36
|
+
if not _condition then
|
|
37
|
+
_condition = value ~= value or value < 0
|
|
38
|
+
end
|
|
39
|
+
if _condition then
|
|
40
|
+
fail(`bad {name}`)
|
|
41
|
+
end
|
|
42
|
+
return value
|
|
43
|
+
end
|
|
44
|
+
local function text(value, name, max)
|
|
45
|
+
if max == nil then
|
|
46
|
+
max = math.huge
|
|
47
|
+
end
|
|
48
|
+
local _value = value
|
|
49
|
+
local _condition = not (type(_value) == "string")
|
|
50
|
+
if not _condition then
|
|
51
|
+
_condition = #value == 0 or #value > max
|
|
52
|
+
end
|
|
53
|
+
if _condition then
|
|
54
|
+
fail(`bad {name}`)
|
|
55
|
+
end
|
|
56
|
+
return value
|
|
57
|
+
end
|
|
58
|
+
--* Counts entries before reading any, stopping at the first one past `cap`.
|
|
59
|
+
local function list(value, name, cap)
|
|
60
|
+
if value == nil then
|
|
61
|
+
return {}
|
|
62
|
+
end
|
|
63
|
+
local _value = value
|
|
64
|
+
if not (type(_value) == "table") then
|
|
65
|
+
fail(`bad {name}`)
|
|
66
|
+
end
|
|
67
|
+
local count = 0
|
|
68
|
+
for key in pairs(value) do
|
|
69
|
+
count += 1
|
|
70
|
+
if count > cap then
|
|
71
|
+
fail(`{name} over {cap}`)
|
|
72
|
+
end
|
|
73
|
+
if not (type(key) == "number") or key % 1 ~= 0 or key < 1 then
|
|
74
|
+
fail(`bad {name}`)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
local items = value
|
|
78
|
+
if #items ~= count then
|
|
79
|
+
fail(`bad {name}`)
|
|
80
|
+
end
|
|
81
|
+
return items
|
|
82
|
+
end
|
|
83
|
+
local function ids(value, name, cap, max)
|
|
84
|
+
local _exp = list(value, name, cap)
|
|
85
|
+
-- ▼ ReadonlyArray.map ▼
|
|
86
|
+
local _newValue = table.create(#_exp)
|
|
87
|
+
local _callback = function(id)
|
|
88
|
+
return text(id, `{name} id`, max)
|
|
89
|
+
end
|
|
90
|
+
for _k, _v in _exp do
|
|
91
|
+
_newValue[_k] = _callback(_v, _k - 1, _exp)
|
|
92
|
+
end
|
|
93
|
+
-- ▲ ReadonlyArray.map ▲
|
|
94
|
+
return _newValue
|
|
95
|
+
end
|
|
96
|
+
local function item(raw, name)
|
|
97
|
+
local _raw = raw
|
|
98
|
+
if not (type(_raw) == "table") then
|
|
99
|
+
fail(`bad {name} item`)
|
|
100
|
+
end
|
|
101
|
+
local t = raw
|
|
102
|
+
return {
|
|
103
|
+
id = text(t.id, `{name} id`, OPERATION_ID_MAX_BYTES),
|
|
104
|
+
name = text(t.name, `{name} name`),
|
|
105
|
+
version = counter(t.version, `{name} version`),
|
|
106
|
+
payload = t.payload,
|
|
107
|
+
generation = counter(t.generation, `{name} generation`),
|
|
108
|
+
attempts = counter(t.attempts, `{name} attempts`),
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
local function readEnvelope(raw, kind)
|
|
112
|
+
local _raw = raw
|
|
113
|
+
if not (type(_raw) == "table") then
|
|
114
|
+
fail("not a table")
|
|
115
|
+
end
|
|
116
|
+
local t = raw
|
|
117
|
+
if t.format ~= 1 then
|
|
118
|
+
fail("bad format")
|
|
119
|
+
end
|
|
120
|
+
if t.kind ~= kind then
|
|
121
|
+
fail("bad kind")
|
|
122
|
+
end
|
|
123
|
+
local payload = t.payload
|
|
124
|
+
if payload ~= nil and not (type(payload) == "table") then
|
|
125
|
+
fail("bad payload")
|
|
126
|
+
end
|
|
127
|
+
local lease
|
|
128
|
+
if t.lease ~= nil then
|
|
129
|
+
local _lease = t.lease
|
|
130
|
+
if not (type(_lease) == "table") then
|
|
131
|
+
fail("bad lease")
|
|
132
|
+
end
|
|
133
|
+
local l = t.lease
|
|
134
|
+
local _object = {
|
|
135
|
+
token = text(l.token, "lease token"),
|
|
136
|
+
serverId = text(l.serverId, "lease server"),
|
|
137
|
+
}
|
|
138
|
+
local _left = "jobId"
|
|
139
|
+
local _jobId = l.jobId
|
|
140
|
+
_object[_left] = if type(_jobId) == "string" then l.jobId else fail("bad lease job")
|
|
141
|
+
_object.expiresAt = time(l.expiresAt, "lease expiry")
|
|
142
|
+
lease = _object
|
|
143
|
+
end
|
|
144
|
+
local commit
|
|
145
|
+
if t.commit ~= nil then
|
|
146
|
+
local _commit = t.commit
|
|
147
|
+
if not (type(_commit) == "table") then
|
|
148
|
+
fail("bad commit")
|
|
149
|
+
end
|
|
150
|
+
local c = t.commit
|
|
151
|
+
commit = {
|
|
152
|
+
token = text(c.token, "commit token"),
|
|
153
|
+
revision = counter(c.revision, "commit revision"),
|
|
154
|
+
op = counter(c.op, "commit op"),
|
|
155
|
+
}
|
|
156
|
+
end
|
|
157
|
+
local _object = {
|
|
158
|
+
format = 1,
|
|
159
|
+
kind = kind,
|
|
160
|
+
schemaVersion = counter(t.schemaVersion, "schemaVersion"),
|
|
161
|
+
payload = payload,
|
|
162
|
+
epoch = counter(t.epoch, "epoch"),
|
|
163
|
+
lease = lease,
|
|
164
|
+
commit = commit,
|
|
165
|
+
receipts = ids(t.receipts, "receipts", MAX_PURCHASE_IDS, PURCHASE_ID_MAX_BYTES),
|
|
166
|
+
}
|
|
167
|
+
local _left = "inbox"
|
|
168
|
+
local _exp = list(t.inbox, "inbox", INBOX_MAX_MESSAGES)
|
|
169
|
+
-- ▼ ReadonlyArray.map ▼
|
|
170
|
+
local _newValue = table.create(#_exp)
|
|
171
|
+
local _callback = function(raw)
|
|
172
|
+
return item(raw, "inbox")
|
|
173
|
+
end
|
|
174
|
+
for _k, _v in _exp do
|
|
175
|
+
_newValue[_k] = _callback(_v, _k - 1, _exp)
|
|
176
|
+
end
|
|
177
|
+
-- ▲ ReadonlyArray.map ▲
|
|
178
|
+
_object[_left] = _newValue
|
|
179
|
+
local _left_1 = "quarantine"
|
|
180
|
+
local _exp_1 = list(t.quarantine, "quarantine", QUARANTINE_MAX_MESSAGES)
|
|
181
|
+
-- ▼ ReadonlyArray.map ▼
|
|
182
|
+
local _newValue_1 = table.create(#_exp_1)
|
|
183
|
+
local _callback_1 = function(raw)
|
|
184
|
+
local reason = raw.reason
|
|
185
|
+
if reason ~= "payload" and reason ~= "attempts" then
|
|
186
|
+
fail("bad quarantine reason")
|
|
187
|
+
end
|
|
188
|
+
local _object_1 = table.clone(item(raw, "quarantine"))
|
|
189
|
+
setmetatable(_object_1, nil)
|
|
190
|
+
_object_1.reason = reason
|
|
191
|
+
return _object_1
|
|
192
|
+
end
|
|
193
|
+
for _k, _v in _exp_1 do
|
|
194
|
+
_newValue_1[_k] = _callback_1(_v, _k - 1, _exp_1)
|
|
195
|
+
end
|
|
196
|
+
-- ▲ ReadonlyArray.map ▲
|
|
197
|
+
_object[_left_1] = _newValue_1
|
|
198
|
+
_object.seen = ids(t.seen, "seen", SEEN_MAX_MESSAGES, OPERATION_ID_MAX_BYTES)
|
|
199
|
+
local _left_2 = "seenFloor"
|
|
200
|
+
local _condition = t.seenFloor
|
|
201
|
+
if _condition == nil then
|
|
202
|
+
_condition = 0
|
|
203
|
+
end
|
|
204
|
+
_object[_left_2] = time(_condition, "seenFloor")
|
|
205
|
+
_object.operations = ids(t.operations, "operations", OPERATION_MARKERS_PER_KEY, OPERATION_ID_MAX_BYTES)
|
|
206
|
+
local _left_3 = "operationFloor"
|
|
207
|
+
local _condition_1 = t.operationFloor
|
|
208
|
+
if _condition_1 == nil then
|
|
209
|
+
_condition_1 = 0
|
|
210
|
+
end
|
|
211
|
+
_object[_left_3] = time(_condition_1, "operationFloor")
|
|
212
|
+
return _object
|
|
213
|
+
end
|
|
214
|
+
--* @internal Validates before anything is interpreted; a malformed envelope is never repaired (rule 40).
|
|
215
|
+
local function parseEnvelope(raw, kind)
|
|
216
|
+
if raw == nil then
|
|
217
|
+
return {
|
|
218
|
+
kind = "absent",
|
|
219
|
+
}
|
|
220
|
+
end
|
|
221
|
+
local ok, result = pcall(readEnvelope, raw, kind)
|
|
222
|
+
if ok then
|
|
223
|
+
return {
|
|
224
|
+
kind = "ok",
|
|
225
|
+
envelope = result,
|
|
226
|
+
}
|
|
227
|
+
end
|
|
228
|
+
local _binding = result
|
|
229
|
+
local marker = _binding[1]
|
|
230
|
+
local reason = _binding[2]
|
|
231
|
+
if marker == UNREADABLE then
|
|
232
|
+
return {
|
|
233
|
+
kind = "unreadable",
|
|
234
|
+
reason = reason,
|
|
235
|
+
}
|
|
236
|
+
end
|
|
237
|
+
error(result)
|
|
238
|
+
end
|
|
239
|
+
--* @internal An absent key: unopened, epoch 0, empty collections.
|
|
240
|
+
local function emptyEnvelope(kind)
|
|
241
|
+
return {
|
|
242
|
+
format = 1,
|
|
243
|
+
kind = kind,
|
|
244
|
+
schemaVersion = 0,
|
|
245
|
+
epoch = 0,
|
|
246
|
+
receipts = {},
|
|
247
|
+
inbox = {},
|
|
248
|
+
quarantine = {},
|
|
249
|
+
seen = {},
|
|
250
|
+
seenFloor = 0,
|
|
251
|
+
operations = {},
|
|
252
|
+
operationFloor = 0,
|
|
253
|
+
}
|
|
254
|
+
end
|
|
255
|
+
local SHORT_ESCAPES = {
|
|
256
|
+
['"'] = true,
|
|
257
|
+
["\\"] = true,
|
|
258
|
+
["\b"] = true,
|
|
259
|
+
["\f"] = true,
|
|
260
|
+
["\n"] = true,
|
|
261
|
+
["\r"] = true,
|
|
262
|
+
["\t"] = true,
|
|
263
|
+
}
|
|
264
|
+
local function stringBytes(value)
|
|
265
|
+
local bytes = #value + 2
|
|
266
|
+
for special in string.gmatch(value, '[%c"\\]') do
|
|
267
|
+
bytes += if SHORT_ESCAPES[special] ~= nil then 1 else 5
|
|
268
|
+
end
|
|
269
|
+
return bytes
|
|
270
|
+
end
|
|
271
|
+
--* @internal Encoded JSON bytes, an upper bound on what Roblox counts (rule 39).
|
|
272
|
+
local function jsonBytes(value)
|
|
273
|
+
local _value = value
|
|
274
|
+
if type(_value) == "string" then
|
|
275
|
+
return stringBytes(value)
|
|
276
|
+
end
|
|
277
|
+
local _value_1 = value
|
|
278
|
+
if type(_value_1) == "number" then
|
|
279
|
+
local _result
|
|
280
|
+
if value % 1 == 0 then
|
|
281
|
+
_result = tostring(value)
|
|
282
|
+
else
|
|
283
|
+
local _value_2 = value
|
|
284
|
+
_result = string.format("%.17g", _value_2)
|
|
285
|
+
end
|
|
286
|
+
return #_result
|
|
287
|
+
end
|
|
288
|
+
local _value_2 = value
|
|
289
|
+
if type(_value_2) == "boolean" then
|
|
290
|
+
return if value then 4 else 5
|
|
291
|
+
end
|
|
292
|
+
local _value_3 = value
|
|
293
|
+
if not (type(_value_3) == "table") then
|
|
294
|
+
return 4
|
|
295
|
+
end
|
|
296
|
+
local t = value
|
|
297
|
+
local size = #t
|
|
298
|
+
local entries = 0
|
|
299
|
+
local bytes = 2
|
|
300
|
+
for key, entry in pairs(t) do
|
|
301
|
+
entries += 1
|
|
302
|
+
bytes += jsonBytes(entry)
|
|
303
|
+
if size == 0 or not (type(key) == "number") then
|
|
304
|
+
bytes += stringBytes(tostring(key)) + 1
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
return bytes + math.max(entries - 1, 0)
|
|
308
|
+
end
|
|
309
|
+
--* @internal
|
|
310
|
+
local function envelopeBytes(envelope)
|
|
311
|
+
return jsonBytes(envelope)
|
|
312
|
+
end
|
|
313
|
+
return {
|
|
314
|
+
parseEnvelope = parseEnvelope,
|
|
315
|
+
emptyEnvelope = emptyEnvelope,
|
|
316
|
+
jsonBytes = jsonBytes,
|
|
317
|
+
envelopeBytes = envelopeBytes,
|
|
318
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|