@trioforge/crucible 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +107 -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 +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/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/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 +16 -0
- package/out/server/init.luau +49 -3
- package/out/server/publish.d.ts +20 -0
- package/out/server/publish.luau +24 -0
- package/out/vide/index.d.ts +7 -0
- package/out/vide/init.luau +50 -0
- package/package.json +13 -2
- package/transformer/data.cjs +72 -0
- package/transformer/declarations.cjs +153 -0
- package/transformer/index.js +37 -11
- package/transformer/network.cjs +961 -0
- package/vide/package.json +4 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local bootClock = TS.import(script, script.Parent.Parent, "boot").bootClock
|
|
4
|
+
local jsonBytes = TS.import(script, script.Parent, "envelope").jsonBytes
|
|
5
|
+
local _limits = TS.import(script, script.Parent, "limits")
|
|
6
|
+
local LOAD_DEADLINE_SECONDS = _limits.LOAD_DEADLINE_SECONDS
|
|
7
|
+
local MAX_PURCHASE_IDS = _limits.MAX_PURCHASE_IDS
|
|
8
|
+
local PURCHASE_ID_MAX_BYTES = _limits.PURCHASE_ID_MAX_BYTES
|
|
9
|
+
local RECEIPT_SAVE_TIMEOUT_SECONDS = _limits.RECEIPT_SAVE_TIMEOUT_SECONDS
|
|
10
|
+
local RECEIPTS_MAX_BYTES = _limits.RECEIPTS_MAX_BYTES
|
|
11
|
+
--* @internal The part of Roblox's receipt info that data reads.
|
|
12
|
+
--* @internal A developer product purchase, as a grant callback sees it.
|
|
13
|
+
--* @internal Stages an effect that runs once, after the grant is durable (rule 61).
|
|
14
|
+
--* @internal
|
|
15
|
+
--* @internal The profile store and grant callback that handle one developer product.
|
|
16
|
+
--* @internal MarketplaceService, as far as the router uses it.
|
|
17
|
+
--[[
|
|
18
|
+
*
|
|
19
|
+
* @internal Crucible's ProcessReceipt (rules 57 to 64). Idempotency rests on two facts: a purchase id and its grant's
|
|
20
|
+
* data change are one revision, and "granted" is returned only once an owned write with the session's token is
|
|
21
|
+
* confirmed to hold the id. Purchase ids are never evicted.
|
|
22
|
+
|
|
23
|
+
]]
|
|
24
|
+
local ReceiptRouter
|
|
25
|
+
do
|
|
26
|
+
ReceiptRouter = setmetatable({}, {
|
|
27
|
+
__tostring = function()
|
|
28
|
+
return "ReceiptRouter"
|
|
29
|
+
end,
|
|
30
|
+
})
|
|
31
|
+
ReceiptRouter.__index = ReceiptRouter
|
|
32
|
+
function ReceiptRouter.new(...)
|
|
33
|
+
local self = setmetatable({}, ReceiptRouter)
|
|
34
|
+
return self:constructor(...) or self
|
|
35
|
+
end
|
|
36
|
+
function ReceiptRouter:constructor(engine)
|
|
37
|
+
self.engine = engine
|
|
38
|
+
self.routes = {}
|
|
39
|
+
self.closed = false
|
|
40
|
+
end
|
|
41
|
+
function ReceiptRouter:route(productId, route)
|
|
42
|
+
local _routes = self.routes
|
|
43
|
+
local _productId = productId
|
|
44
|
+
local _route = route
|
|
45
|
+
_routes[_productId] = _route
|
|
46
|
+
end
|
|
47
|
+
function ReceiptRouter:unroute(productId)
|
|
48
|
+
local _routes = self.routes
|
|
49
|
+
local _productId = productId
|
|
50
|
+
_routes[_productId] = nil
|
|
51
|
+
end
|
|
52
|
+
function ReceiptRouter:install(service)
|
|
53
|
+
self.service = service
|
|
54
|
+
service.ProcessReceipt = function(info)
|
|
55
|
+
return self:decision(self:process(info))
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
function ReceiptRouter:decision(result)
|
|
59
|
+
return if result == "granted" then Enum.ProductPurchaseDecision.PurchaseGranted else Enum.ProductPurchaseDecision.NotProcessedYet
|
|
60
|
+
end
|
|
61
|
+
function ReceiptRouter:close(retire)
|
|
62
|
+
if retire == nil then
|
|
63
|
+
retire = false
|
|
64
|
+
end
|
|
65
|
+
self.closed = true
|
|
66
|
+
if retire and self.service ~= nil then
|
|
67
|
+
self.service.ProcessReceipt = function()
|
|
68
|
+
return Enum.ProductPurchaseDecision.NotProcessedYet
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
function ReceiptRouter:process(info)
|
|
73
|
+
local engine = self.engine
|
|
74
|
+
local holdUntil = bootClock() + LOAD_DEADLINE_SECONDS
|
|
75
|
+
while true do
|
|
76
|
+
if self.closed then
|
|
77
|
+
return "notYet"
|
|
78
|
+
end
|
|
79
|
+
local _routes = self.routes
|
|
80
|
+
local _productId = info.ProductId
|
|
81
|
+
local route = _routes[_productId]
|
|
82
|
+
if route == nil then
|
|
83
|
+
engine:diagnose(`product:{info.ProductId}`, `a purchase of product {info.ProductId} was refused: no grant handles it`)
|
|
84
|
+
return "notYet"
|
|
85
|
+
end
|
|
86
|
+
local id = info.PurchaseId
|
|
87
|
+
if not (type(id) == "string") or #id < 1 or #id > PURCHASE_ID_MAX_BYTES then
|
|
88
|
+
engine:diagnose(`receipt-id:{info.ProductId}`, `a purchase of product {info.ProductId} was refused: its purchase id is not 1 to {PURCHASE_ID_MAX_BYTES} bytes`)
|
|
89
|
+
return "notYet"
|
|
90
|
+
end
|
|
91
|
+
local player = engine.port.players.byUserId(info.PlayerId)
|
|
92
|
+
if player == nil then
|
|
93
|
+
return "notYet"
|
|
94
|
+
end
|
|
95
|
+
local runtime = route.runtime
|
|
96
|
+
local session = runtime.sessions[player]
|
|
97
|
+
if session == nil or session.phase == 0 or session.sealedBy == "handoff" then
|
|
98
|
+
if bootClock() >= holdUntil then
|
|
99
|
+
return "notYet"
|
|
100
|
+
end
|
|
101
|
+
runtime.changed:park(holdUntil)
|
|
102
|
+
continue
|
|
103
|
+
end
|
|
104
|
+
if not runtime:writable(player) then
|
|
105
|
+
return "notYet"
|
|
106
|
+
end
|
|
107
|
+
if session.durable[id] ~= nil then
|
|
108
|
+
return self:granted(session, id)
|
|
109
|
+
end
|
|
110
|
+
return self:save(route, session, player, id, info)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
function ReceiptRouter:save(route, session, player, id, info)
|
|
114
|
+
local runtime = route.runtime
|
|
115
|
+
local engine = self.engine
|
|
116
|
+
local _receipts = session.receipts
|
|
117
|
+
local _id = id
|
|
118
|
+
if not (table.find(_receipts, _id) ~= nil) then
|
|
119
|
+
if #session.receipts >= MAX_PURCHASE_IDS or jsonBytes(session.receipts) + jsonBytes(id) + 1 > RECEIPTS_MAX_BYTES then
|
|
120
|
+
engine:diagnose(`receipt-capacity:{session.token}`, `receipt capacity reached for {runtime.schema.name} {session.userId}`)
|
|
121
|
+
return "notYet"
|
|
122
|
+
end
|
|
123
|
+
local purchase = {
|
|
124
|
+
id = id,
|
|
125
|
+
productId = info.ProductId,
|
|
126
|
+
currencySpent = info.CurrencySpent,
|
|
127
|
+
}
|
|
128
|
+
local effects = {}
|
|
129
|
+
local afterGrant = function(effect)
|
|
130
|
+
local _effect = effect
|
|
131
|
+
if not (type(_effect) == "function") then
|
|
132
|
+
error("afterGrant takes a function", 2)
|
|
133
|
+
end
|
|
134
|
+
local _effect_1 = effect
|
|
135
|
+
table.insert(effects, _effect_1)
|
|
136
|
+
end
|
|
137
|
+
local ok = pcall(function()
|
|
138
|
+
return session:write(function(data)
|
|
139
|
+
return route.grant(player, data, purchase, afterGrant)
|
|
140
|
+
end, function()
|
|
141
|
+
session:stage(id)
|
|
142
|
+
local _effects = session.effects
|
|
143
|
+
local _id_1 = id
|
|
144
|
+
_effects[_id_1] = effects
|
|
145
|
+
end)
|
|
146
|
+
end)
|
|
147
|
+
if not ok then
|
|
148
|
+
engine:diagnose(`grant:{runtime.schema.name}:{info.ProductId}`, `{runtime.schema.name}: the grant for product {info.ProductId} threw, yielded or returned an invalid value; nothing was stored`)
|
|
149
|
+
return "notYet"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
runtime:requestWrite(session, 2)
|
|
153
|
+
local timeout = bootClock() + RECEIPT_SAVE_TIMEOUT_SECONDS
|
|
154
|
+
while true do
|
|
155
|
+
local _durable = session.durable
|
|
156
|
+
local _id_1 = id
|
|
157
|
+
local _condition = not (_durable[_id_1] ~= nil)
|
|
158
|
+
if _condition then
|
|
159
|
+
_condition = session.phase ~= 3 and bootClock() < timeout and not engine.settled
|
|
160
|
+
end
|
|
161
|
+
if not _condition then
|
|
162
|
+
break
|
|
163
|
+
end
|
|
164
|
+
runtime.changed:park(timeout)
|
|
165
|
+
end
|
|
166
|
+
local _durable = session.durable
|
|
167
|
+
local _id_1 = id
|
|
168
|
+
return if _durable[_id_1] ~= nil then self:granted(session, id) else "notYet"
|
|
169
|
+
end
|
|
170
|
+
function ReceiptRouter:granted(session, id)
|
|
171
|
+
local _effects = session.effects
|
|
172
|
+
local _id = id
|
|
173
|
+
local effects = _effects[_id]
|
|
174
|
+
local _effects_1 = session.effects
|
|
175
|
+
local _id_1 = id
|
|
176
|
+
_effects_1[_id_1] = nil
|
|
177
|
+
for _, effect in effects or {} do
|
|
178
|
+
self.engine.port.spawn(function()
|
|
179
|
+
local ok = pcall(effect)
|
|
180
|
+
if not ok then
|
|
181
|
+
self.engine:diagnose(`effect:{session.token}:{id}`, `an afterGrant effect for {session.schema.name} {session.userId} threw`)
|
|
182
|
+
end
|
|
183
|
+
end)
|
|
184
|
+
end
|
|
185
|
+
return "granted"
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
return {
|
|
189
|
+
ReceiptRouter = ReceiptRouter,
|
|
190
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local bootClock = TS.import(script, script.Parent.Parent, "boot").bootClock
|
|
4
|
+
local CALL_DEADLINE_SECONDS = TS.import(script, script.Parent, "limits").CALL_DEADLINE_SECONDS
|
|
5
|
+
local dataPort = TS.import(script, script.Parent, "port").dataPort
|
|
6
|
+
local _record_declarations = TS.import(script, script.Parent, "record-declarations")
|
|
7
|
+
local declaredRecords = _record_declarations.declaredRecords
|
|
8
|
+
local installRecordDriver = _record_declarations.installRecordDriver
|
|
9
|
+
local RecordStore = TS.import(script, script.Parent, "records").RecordStore
|
|
10
|
+
local Waiters = TS.import(script, script.Parent, "waiters").Waiters
|
|
11
|
+
local bound = {}
|
|
12
|
+
local activation
|
|
13
|
+
--* @internal Rule 104: gives every declared record store its engine, and wakes calls waiting for activation.
|
|
14
|
+
local function bindRecords(engine)
|
|
15
|
+
for name, declaration in declaredRecords() do
|
|
16
|
+
local _recordStore = RecordStore.new(engine, declaration.schema)
|
|
17
|
+
bound[name] = _recordStore
|
|
18
|
+
end
|
|
19
|
+
local _result = activation
|
|
20
|
+
if _result ~= nil then
|
|
21
|
+
_result:wake()
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
--* @internal
|
|
25
|
+
local function unbindRecords()
|
|
26
|
+
table.clear(bound)
|
|
27
|
+
end
|
|
28
|
+
--* Rule 4: before activation a call waits for it, within its own deadline.
|
|
29
|
+
local function activated(name)
|
|
30
|
+
local deadline = bootClock() + CALL_DEADLINE_SECONDS
|
|
31
|
+
while true do
|
|
32
|
+
local _name = name
|
|
33
|
+
local _condition = bound[_name] == nil
|
|
34
|
+
if _condition then
|
|
35
|
+
_condition = bootClock() < deadline
|
|
36
|
+
end
|
|
37
|
+
if not _condition then
|
|
38
|
+
break
|
|
39
|
+
end
|
|
40
|
+
if activation == nil then
|
|
41
|
+
activation = Waiters.new(dataPort())
|
|
42
|
+
end
|
|
43
|
+
activation:park(deadline)
|
|
44
|
+
end
|
|
45
|
+
local _name = name
|
|
46
|
+
return bound[_name]
|
|
47
|
+
end
|
|
48
|
+
local function publicResult(result)
|
|
49
|
+
return if result.kind == "ok" then {
|
|
50
|
+
ok = true,
|
|
51
|
+
value = result.value,
|
|
52
|
+
} else {
|
|
53
|
+
ok = false,
|
|
54
|
+
reason = result.kind,
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
local function publicUpdate(result)
|
|
58
|
+
return if result.kind == "ok" then {
|
|
59
|
+
ok = true,
|
|
60
|
+
value = result.value,
|
|
61
|
+
replayed = result.replayed,
|
|
62
|
+
} else {
|
|
63
|
+
ok = false,
|
|
64
|
+
reason = result.kind,
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
installRecordDriver({
|
|
68
|
+
read = function(self, name, key)
|
|
69
|
+
local store = activated(name)
|
|
70
|
+
if store == nil then
|
|
71
|
+
return {
|
|
72
|
+
ok = false,
|
|
73
|
+
reason = "timeout",
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
return publicResult(store:read(key))
|
|
77
|
+
end,
|
|
78
|
+
update = function(self, name, key, change, id)
|
|
79
|
+
local store = activated(name)
|
|
80
|
+
if store == nil then
|
|
81
|
+
return {
|
|
82
|
+
ok = false,
|
|
83
|
+
reason = "timeout",
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
return publicUpdate(store:update(key, change, id))
|
|
87
|
+
end,
|
|
88
|
+
})
|
|
89
|
+
return {
|
|
90
|
+
bindRecords = bindRecords,
|
|
91
|
+
unbindRecords = unbindRecords,
|
|
92
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local sealSchema = TS.import(script, script.Parent, "admission").sealSchema
|
|
4
|
+
local _limits = TS.import(script, script.Parent, "limits")
|
|
5
|
+
local MAX_DATA_DECLARATIONS = _limits.MAX_DATA_DECLARATIONS
|
|
6
|
+
local STORE_NAME_MAX_BYTES = _limits.STORE_NAME_MAX_BYTES
|
|
7
|
+
--* @internal
|
|
8
|
+
--[[
|
|
9
|
+
*
|
|
10
|
+
* @internal What a declared store's calls run on once DataRuntime activates. This module is reached from the emit
|
|
11
|
+
* entry, so it imports nothing that loads boot; `record-bindings.ts` installs the driver.
|
|
12
|
+
|
|
13
|
+
]]
|
|
14
|
+
local declarations = {}
|
|
15
|
+
local sealed = false
|
|
16
|
+
local driver
|
|
17
|
+
local profiles = {}
|
|
18
|
+
--* @internal Rule 2: profile and generic stores share one index of store names.
|
|
19
|
+
local function storeNameTaken(name)
|
|
20
|
+
local _name = name
|
|
21
|
+
local _condition = declarations[_name] ~= nil
|
|
22
|
+
if not _condition then
|
|
23
|
+
local _name_1 = name
|
|
24
|
+
_condition = profiles[_name_1] ~= nil
|
|
25
|
+
end
|
|
26
|
+
return _condition
|
|
27
|
+
end
|
|
28
|
+
--* @internal Rule 3: every data declaration in this realm counts toward MAX_DATA_DECLARATIONS.
|
|
29
|
+
local function dataDeclarationCount()
|
|
30
|
+
-- ▼ ReadonlyMap.size ▼
|
|
31
|
+
local _size = 0
|
|
32
|
+
for _ in declarations do
|
|
33
|
+
_size += 1
|
|
34
|
+
end
|
|
35
|
+
-- ▲ ReadonlyMap.size ▲
|
|
36
|
+
-- ▼ ReadonlySet.size ▼
|
|
37
|
+
local _size_1 = 0
|
|
38
|
+
for _ in profiles do
|
|
39
|
+
_size_1 += 1
|
|
40
|
+
end
|
|
41
|
+
-- ▲ ReadonlySet.size ▲
|
|
42
|
+
return _size + _size_1
|
|
43
|
+
end
|
|
44
|
+
--* @internal
|
|
45
|
+
local function registerProfileName(name)
|
|
46
|
+
local _name = name
|
|
47
|
+
profiles[_name] = true
|
|
48
|
+
end
|
|
49
|
+
--* @internal True after DataRuntime constructed (rule 3).
|
|
50
|
+
local function dataRegistrySealed()
|
|
51
|
+
return sealed
|
|
52
|
+
end
|
|
53
|
+
--* @internal Rule 2's grammar: dot-separated `[A-Za-z][A-Za-z0-9_]*` segments, with `Crucible` reserved as the first.
|
|
54
|
+
local function checkStoreName(name)
|
|
55
|
+
if #name == 0 or #name > STORE_NAME_MAX_BYTES then
|
|
56
|
+
error(`{name}: a store name is 1 to {STORE_NAME_MAX_BYTES} bytes`, 4)
|
|
57
|
+
end
|
|
58
|
+
local segments = string.split(name, ".")
|
|
59
|
+
for _, segment in segments do
|
|
60
|
+
if (string.match(segment, "^[A-Za-z][A-Za-z0-9_]*$")) == nil then
|
|
61
|
+
error(`{name}: a store name is dot-separated segments of letters, digits and underscores`, 4)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
if segments[1] == "Crucible" then
|
|
65
|
+
error(`{name}: the name Crucible is reserved`, 4)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
--* @internal Rule 3: DataRuntime seals the registry when it constructs; a failed boot unseals it.
|
|
69
|
+
local function sealDeclarations(value)
|
|
70
|
+
sealed = value
|
|
71
|
+
end
|
|
72
|
+
--* @internal The declarations a DataRuntime binds at activation (rule 104).
|
|
73
|
+
local function declaredRecords()
|
|
74
|
+
return declarations
|
|
75
|
+
end
|
|
76
|
+
--* @internal
|
|
77
|
+
local function installRecordDriver(value)
|
|
78
|
+
driver = value
|
|
79
|
+
end
|
|
80
|
+
local function installed(name)
|
|
81
|
+
if driver == nil then
|
|
82
|
+
error(`{name}: the data runtime is not loaded; import @trioforge/crucible/server`, 3)
|
|
83
|
+
end
|
|
84
|
+
return driver
|
|
85
|
+
end
|
|
86
|
+
--[[
|
|
87
|
+
*
|
|
88
|
+
* @internal Rules 4 and 85: the emit form's body. Admits and freezes `defaults` with `value`, registers the name, and
|
|
89
|
+
* returns a frozen handle whose two calls bind to the engine at activation.
|
|
90
|
+
|
|
91
|
+
]]
|
|
92
|
+
local function declareRecords(name, defaults, migrations, value)
|
|
93
|
+
checkStoreName(name)
|
|
94
|
+
if sealed then
|
|
95
|
+
error(`{name} was declared after the data registry sealed; declare stores in modules that boot loads`, 3)
|
|
96
|
+
end
|
|
97
|
+
if storeNameTaken(name) then
|
|
98
|
+
error(`{name} is already declared`, 3)
|
|
99
|
+
end
|
|
100
|
+
if dataDeclarationCount() >= MAX_DATA_DECLARATIONS then
|
|
101
|
+
error(`{name}: more than {MAX_DATA_DECLARATIONS} data declarations`, 3)
|
|
102
|
+
end
|
|
103
|
+
local _array = {}
|
|
104
|
+
local _length = #_array
|
|
105
|
+
local _array_1 = (migrations or {})
|
|
106
|
+
table.move(_array_1, 1, #_array_1, _length + 1, _array)
|
|
107
|
+
local steps = _array
|
|
108
|
+
local schema = sealSchema({
|
|
109
|
+
name = name,
|
|
110
|
+
version = #steps,
|
|
111
|
+
defaults = defaults,
|
|
112
|
+
node = value,
|
|
113
|
+
migrations = steps,
|
|
114
|
+
})
|
|
115
|
+
local _name = name
|
|
116
|
+
local _arg1 = {
|
|
117
|
+
name = name,
|
|
118
|
+
schema = schema,
|
|
119
|
+
}
|
|
120
|
+
declarations[_name] = _arg1
|
|
121
|
+
return table.freeze({
|
|
122
|
+
read = function(self, key)
|
|
123
|
+
return installed(name):read(name, key)
|
|
124
|
+
end,
|
|
125
|
+
update = function(self, key, change, id)
|
|
126
|
+
return installed(name):update(name, key, change, id)
|
|
127
|
+
end,
|
|
128
|
+
})
|
|
129
|
+
end
|
|
130
|
+
return {
|
|
131
|
+
storeNameTaken = storeNameTaken,
|
|
132
|
+
dataDeclarationCount = dataDeclarationCount,
|
|
133
|
+
registerProfileName = registerProfileName,
|
|
134
|
+
dataRegistrySealed = dataRegistrySealed,
|
|
135
|
+
checkStoreName = checkStoreName,
|
|
136
|
+
sealDeclarations = sealDeclarations,
|
|
137
|
+
declaredRecords = declaredRecords,
|
|
138
|
+
installRecordDriver = installRecordDriver,
|
|
139
|
+
declareRecords = declareRecords,
|
|
140
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|