@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,304 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local _envelope = TS.import(script, script.Parent, "envelope")
|
|
4
|
+
local emptyEnvelope = _envelope.emptyEnvelope
|
|
5
|
+
local envelopeBytes = _envelope.envelopeBytes
|
|
6
|
+
local parseEnvelope = _envelope.parseEnvelope
|
|
7
|
+
local _inbox_transforms = TS.import(script, script.Parent, "inbox-transforms")
|
|
8
|
+
local applyAcks = _inbox_transforms.applyAcks
|
|
9
|
+
local applyMarks = _inbox_transforms.applyMarks
|
|
10
|
+
local _limits = TS.import(script, script.Parent, "limits")
|
|
11
|
+
local LEASE_SECONDS = _limits.LEASE_SECONDS
|
|
12
|
+
local MAX_ENVELOPE_BYTES = _limits.MAX_ENVELOPE_BYTES
|
|
13
|
+
local TAKEOVER_GRACE_SECONDS = _limits.TAKEOVER_GRACE_SECONDS
|
|
14
|
+
--* @internal What an owned write or a carrying acquisition stores for a session: its newest state (rules 27 to 29).
|
|
15
|
+
--* @internal A sealed session of this server whose release settled, keyed by its token in `supersedes` (rule 29).
|
|
16
|
+
--* @internal
|
|
17
|
+
--* @internal
|
|
18
|
+
--* @internal
|
|
19
|
+
--* @internal
|
|
20
|
+
local function union(stored, staged)
|
|
21
|
+
local _set = {}
|
|
22
|
+
for _, _v in stored do
|
|
23
|
+
_set[_v] = true
|
|
24
|
+
end
|
|
25
|
+
local seen = _set
|
|
26
|
+
local _array = {}
|
|
27
|
+
local _length = #_array
|
|
28
|
+
table.move(stored, 1, #stored, _length + 1, _array)
|
|
29
|
+
local result = _array
|
|
30
|
+
for _, id in staged do
|
|
31
|
+
if not (seen[id] ~= nil) then
|
|
32
|
+
seen[id] = true
|
|
33
|
+
table.insert(result, id)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
return result
|
|
37
|
+
end
|
|
38
|
+
--* @internal
|
|
39
|
+
local function stored(current, userId, envelope)
|
|
40
|
+
local _array = {}
|
|
41
|
+
local _length = #_array
|
|
42
|
+
local _result = current
|
|
43
|
+
if _result ~= nil then
|
|
44
|
+
_result = _result.userIds
|
|
45
|
+
end
|
|
46
|
+
local _condition = _result
|
|
47
|
+
if _condition == nil then
|
|
48
|
+
_condition = {}
|
|
49
|
+
end
|
|
50
|
+
table.move(_condition, 1, #_condition, _length + 1, _array)
|
|
51
|
+
local userIds = _array
|
|
52
|
+
local _userId = userId
|
|
53
|
+
if not (table.find(userIds, _userId) ~= nil) then
|
|
54
|
+
local _userId_1 = userId
|
|
55
|
+
table.insert(userIds, _userId_1)
|
|
56
|
+
end
|
|
57
|
+
local _object = {
|
|
58
|
+
value = envelope,
|
|
59
|
+
userIds = userIds,
|
|
60
|
+
}
|
|
61
|
+
local _left = "metadata"
|
|
62
|
+
local _result_1 = current
|
|
63
|
+
if _result_1 ~= nil then
|
|
64
|
+
_result_1 = _result_1.metadata
|
|
65
|
+
end
|
|
66
|
+
local _condition_1 = _result_1
|
|
67
|
+
if _condition_1 == nil then
|
|
68
|
+
_condition_1 = {}
|
|
69
|
+
end
|
|
70
|
+
_object[_left] = _condition_1
|
|
71
|
+
return _object
|
|
72
|
+
end
|
|
73
|
+
local function withSnapshot(envelope, snapshot, commit)
|
|
74
|
+
local _object = table.clone(envelope)
|
|
75
|
+
setmetatable(_object, nil)
|
|
76
|
+
_object.payload = snapshot.payload
|
|
77
|
+
_object.schemaVersion = snapshot.schemaVersion
|
|
78
|
+
_object.receipts = union(envelope.receipts, snapshot.receipts)
|
|
79
|
+
_object.commit = commit
|
|
80
|
+
return applyMarks(applyAcks(_object, snapshot.acks), snapshot.marks)
|
|
81
|
+
end
|
|
82
|
+
--* @internal Takes the lease only when absent, our own settled sealed token's, or past expiry plus grace (rule 24).
|
|
83
|
+
local function acquire(current, input, record)
|
|
84
|
+
local _result = current
|
|
85
|
+
if _result ~= nil then
|
|
86
|
+
_result = _result.value
|
|
87
|
+
end
|
|
88
|
+
local parsed = parseEnvelope(_result, "profiles")
|
|
89
|
+
if parsed.kind == "unreadable" then
|
|
90
|
+
record(parsed)
|
|
91
|
+
return nil
|
|
92
|
+
end
|
|
93
|
+
local envelope = if parsed.kind == "ok" then parsed.envelope else emptyEnvelope("profiles")
|
|
94
|
+
if envelope.payload ~= nil and envelope.schemaVersion > input.schemaVersion then
|
|
95
|
+
record({
|
|
96
|
+
kind = "newer",
|
|
97
|
+
version = envelope.schemaVersion,
|
|
98
|
+
})
|
|
99
|
+
return nil
|
|
100
|
+
end
|
|
101
|
+
local lease = envelope.lease
|
|
102
|
+
local _result_1 = lease
|
|
103
|
+
if _result_1 ~= nil then
|
|
104
|
+
_result_1 = _result_1.token
|
|
105
|
+
end
|
|
106
|
+
if _result_1 == input.token then
|
|
107
|
+
record({
|
|
108
|
+
kind = "already",
|
|
109
|
+
envelope = envelope,
|
|
110
|
+
})
|
|
111
|
+
return nil
|
|
112
|
+
end
|
|
113
|
+
local _result_2
|
|
114
|
+
if lease == nil then
|
|
115
|
+
_result_2 = nil
|
|
116
|
+
else
|
|
117
|
+
local _supersedes = input.supersedes
|
|
118
|
+
local _token = lease.token
|
|
119
|
+
_result_2 = _supersedes[_token]
|
|
120
|
+
end
|
|
121
|
+
local sealed = _result_2
|
|
122
|
+
local wall = input.wall()
|
|
123
|
+
if lease ~= nil and sealed == nil and wall <= lease.expiresAt + TAKEOVER_GRACE_SECONDS then
|
|
124
|
+
record({
|
|
125
|
+
kind = "held",
|
|
126
|
+
lease = lease,
|
|
127
|
+
})
|
|
128
|
+
return nil
|
|
129
|
+
end
|
|
130
|
+
local _object = table.clone(envelope)
|
|
131
|
+
setmetatable(_object, nil)
|
|
132
|
+
_object.epoch = envelope.epoch + 1
|
|
133
|
+
_object.lease = {
|
|
134
|
+
token = input.token,
|
|
135
|
+
serverId = input.serverId,
|
|
136
|
+
jobId = input.jobId,
|
|
137
|
+
expiresAt = wall + LEASE_SECONDS,
|
|
138
|
+
}
|
|
139
|
+
local written = _object
|
|
140
|
+
local commit = envelope.commit
|
|
141
|
+
local _condition = sealed ~= nil and envelope.epoch == sealed.epoch
|
|
142
|
+
if _condition then
|
|
143
|
+
local _result_3 = commit
|
|
144
|
+
if _result_3 ~= nil then
|
|
145
|
+
_result_3 = _result_3.token
|
|
146
|
+
end
|
|
147
|
+
local _condition_1 = _result_3 ~= lease.token
|
|
148
|
+
if not _condition_1 then
|
|
149
|
+
_condition_1 = commit.revision < sealed.snapshot.revision
|
|
150
|
+
end
|
|
151
|
+
_condition = _condition_1
|
|
152
|
+
end
|
|
153
|
+
local carried = _condition
|
|
154
|
+
if carried then
|
|
155
|
+
local _exp = written
|
|
156
|
+
local _exp_1 = sealed.snapshot
|
|
157
|
+
local _object_1 = {
|
|
158
|
+
token = lease.token,
|
|
159
|
+
revision = sealed.snapshot.revision,
|
|
160
|
+
}
|
|
161
|
+
local _left = "op"
|
|
162
|
+
local _result_3 = commit
|
|
163
|
+
if _result_3 ~= nil then
|
|
164
|
+
_result_3 = _result_3.token
|
|
165
|
+
end
|
|
166
|
+
_object_1[_left] = if _result_3 == lease.token then commit.op else 0
|
|
167
|
+
written = withSnapshot(_exp, _exp_1, _object_1)
|
|
168
|
+
end
|
|
169
|
+
local bytes = envelopeBytes(written)
|
|
170
|
+
if bytes > MAX_ENVELOPE_BYTES then
|
|
171
|
+
record({
|
|
172
|
+
kind = "unreadable",
|
|
173
|
+
reason = `envelope {bytes} bytes`,
|
|
174
|
+
})
|
|
175
|
+
return nil
|
|
176
|
+
end
|
|
177
|
+
record({
|
|
178
|
+
kind = "taken",
|
|
179
|
+
envelope = written,
|
|
180
|
+
carried = carried,
|
|
181
|
+
})
|
|
182
|
+
return stored(current, input.userId, written)
|
|
183
|
+
end
|
|
184
|
+
--* @internal A renewal, save, handoff or release: fenced on token and epoch, identified by `op` (rules 27, 28).
|
|
185
|
+
local function ownedWrite(current, input, record)
|
|
186
|
+
local _result = current
|
|
187
|
+
if _result ~= nil then
|
|
188
|
+
_result = _result.value
|
|
189
|
+
end
|
|
190
|
+
local parsed = parseEnvelope(_result, "profiles")
|
|
191
|
+
if parsed.kind == "unreadable" then
|
|
192
|
+
record(parsed)
|
|
193
|
+
return nil
|
|
194
|
+
end
|
|
195
|
+
if parsed.kind == "absent" then
|
|
196
|
+
record({
|
|
197
|
+
kind = "fenced",
|
|
198
|
+
})
|
|
199
|
+
return nil
|
|
200
|
+
end
|
|
201
|
+
local envelope = parsed.envelope
|
|
202
|
+
local commit = envelope.commit
|
|
203
|
+
local _result_1 = commit
|
|
204
|
+
if _result_1 ~= nil then
|
|
205
|
+
_result_1 = _result_1.token
|
|
206
|
+
end
|
|
207
|
+
local _condition = _result_1 == input.token
|
|
208
|
+
if _condition then
|
|
209
|
+
_condition = commit.op >= input.op
|
|
210
|
+
end
|
|
211
|
+
if _condition then
|
|
212
|
+
record({
|
|
213
|
+
kind = "already",
|
|
214
|
+
envelope = envelope,
|
|
215
|
+
})
|
|
216
|
+
return nil
|
|
217
|
+
end
|
|
218
|
+
local lease = envelope.lease
|
|
219
|
+
if lease == nil or lease.token ~= input.token or envelope.epoch ~= input.epoch or (envelope.payload ~= nil and envelope.schemaVersion > input.snapshot.schemaVersion) then
|
|
220
|
+
record({
|
|
221
|
+
kind = "fenced",
|
|
222
|
+
lease = lease,
|
|
223
|
+
})
|
|
224
|
+
return nil
|
|
225
|
+
end
|
|
226
|
+
local wall = input.wall()
|
|
227
|
+
if wall >= lease.expiresAt then
|
|
228
|
+
record({
|
|
229
|
+
kind = "expired",
|
|
230
|
+
})
|
|
231
|
+
return nil
|
|
232
|
+
end
|
|
233
|
+
local _result_2 = commit
|
|
234
|
+
if _result_2 ~= nil then
|
|
235
|
+
_result_2 = _result_2.token
|
|
236
|
+
end
|
|
237
|
+
local revision = if _result_2 == input.token then commit.revision else -1
|
|
238
|
+
local marker = {
|
|
239
|
+
token = input.token,
|
|
240
|
+
revision = math.max(input.snapshot.revision, revision),
|
|
241
|
+
op = input.op,
|
|
242
|
+
}
|
|
243
|
+
local _object = table.clone((if input.snapshot.revision > revision then withSnapshot(envelope, input.snapshot, marker) else applyMarks(envelope, input.snapshot.marks)))
|
|
244
|
+
setmetatable(_object, nil)
|
|
245
|
+
local _left = "lease"
|
|
246
|
+
local _result_3
|
|
247
|
+
if input.release then
|
|
248
|
+
_result_3 = nil
|
|
249
|
+
else
|
|
250
|
+
local _object_1 = table.clone(lease)
|
|
251
|
+
setmetatable(_object_1, nil)
|
|
252
|
+
_object_1.expiresAt = wall + LEASE_SECONDS
|
|
253
|
+
_result_3 = _object_1
|
|
254
|
+
end
|
|
255
|
+
_object[_left] = _result_3
|
|
256
|
+
_object.commit = marker
|
|
257
|
+
local written = _object
|
|
258
|
+
local bytes = envelopeBytes(written)
|
|
259
|
+
if bytes > MAX_ENVELOPE_BYTES then
|
|
260
|
+
record({
|
|
261
|
+
kind = "too-large",
|
|
262
|
+
bytes = bytes,
|
|
263
|
+
})
|
|
264
|
+
return nil
|
|
265
|
+
end
|
|
266
|
+
record({
|
|
267
|
+
kind = "written",
|
|
268
|
+
envelope = written,
|
|
269
|
+
})
|
|
270
|
+
return stored(current, input.userId, written)
|
|
271
|
+
end
|
|
272
|
+
--* @internal Removes exactly the abandoned load's own lease and writes nothing else (rule 20).
|
|
273
|
+
local function abandonAcquisition(current, userId, token, record)
|
|
274
|
+
local _result = current
|
|
275
|
+
if _result ~= nil then
|
|
276
|
+
_result = _result.value
|
|
277
|
+
end
|
|
278
|
+
local parsed = parseEnvelope(_result, "profiles")
|
|
279
|
+
if parsed.kind ~= "ok" then
|
|
280
|
+
record("absent")
|
|
281
|
+
return nil
|
|
282
|
+
end
|
|
283
|
+
local _result_1 = parsed.envelope.lease
|
|
284
|
+
if _result_1 ~= nil then
|
|
285
|
+
_result_1 = _result_1.token
|
|
286
|
+
end
|
|
287
|
+
if _result_1 ~= token then
|
|
288
|
+
record("foreign")
|
|
289
|
+
return nil
|
|
290
|
+
end
|
|
291
|
+
record("released")
|
|
292
|
+
local _exp = current
|
|
293
|
+
local _exp_1 = userId
|
|
294
|
+
local _object = table.clone(parsed.envelope)
|
|
295
|
+
setmetatable(_object, nil)
|
|
296
|
+
_object.lease = nil
|
|
297
|
+
return stored(_exp, _exp_1, _object)
|
|
298
|
+
end
|
|
299
|
+
return {
|
|
300
|
+
stored = stored,
|
|
301
|
+
acquire = acquire,
|
|
302
|
+
ownedWrite = ownedWrite,
|
|
303
|
+
abandonAcquisition = abandonAcquisition,
|
|
304
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
--* @internal Lease and load (rules 19 to 24).
|
|
3
|
+
local LEASE_SECONDS = 90
|
|
4
|
+
--* @internal
|
|
5
|
+
local LOCAL_AUTHORITY_SECONDS = 70
|
|
6
|
+
--* @internal
|
|
7
|
+
local RENEW_INTERVAL_SECONDS = 20
|
|
8
|
+
--* @internal
|
|
9
|
+
local RENEW_JITTER_SECONDS = 2
|
|
10
|
+
--* @internal
|
|
11
|
+
local TAKEOVER_GRACE_SECONDS = 10
|
|
12
|
+
--* @internal Assumed, not measured (rule 25).
|
|
13
|
+
local CLOCK_ERROR_ASSUMED_SECONDS = 5
|
|
14
|
+
--* @internal Expiry 90 + grace 10 + clock error 5 = 105, plus one 5 s poll and request time.
|
|
15
|
+
local LOAD_DEADLINE_SECONDS = 120
|
|
16
|
+
--* @internal The last entry repeats.
|
|
17
|
+
local LOAD_POLL_SCHEDULE_SECONDS = { 1, 1, 2, 2, 3, 5 }
|
|
18
|
+
--* @internal Each poll is multiplied by uniform [1 - this, 1 + this].
|
|
19
|
+
local LOAD_POLL_JITTER = 0.25
|
|
20
|
+
--* @internal
|
|
21
|
+
local LOAD_POLL_MIN_SECONDS = 0.25
|
|
22
|
+
--* @internal A renewal whose safe bound is nearer than this dispatches as LeaseCritical (rule 34).
|
|
23
|
+
local LEASE_CRITICAL_SECONDS = 30
|
|
24
|
+
--* @internal Saves and barriers (rules 37, 45).
|
|
25
|
+
local SAVE_TIMEOUT_DEFAULT_SECONDS = 10
|
|
26
|
+
--* @internal
|
|
27
|
+
local SAVE_TIMEOUT_MAX_SECONDS = 30
|
|
28
|
+
--* @internal
|
|
29
|
+
local HANDOFF_TIMEOUT_SECONDS = 10
|
|
30
|
+
--* @internal How long a granting callback waits for its id to be durable (rule 63).
|
|
31
|
+
local RECEIPT_SAVE_TIMEOUT_SECONDS = 30
|
|
32
|
+
--* @internal
|
|
33
|
+
local CALL_DEADLINE_SECONDS = 30
|
|
34
|
+
--* @internal Scheduler (rules 32 to 36).
|
|
35
|
+
local MAX_IN_FLIGHT = 8
|
|
36
|
+
--* @internal
|
|
37
|
+
local RESERVED_SESSION_SLOTS = 4
|
|
38
|
+
--* @internal
|
|
39
|
+
local ORDINARY_MAX_IN_FLIGHT = 3
|
|
40
|
+
--* @internal
|
|
41
|
+
local MAX_PENDING_OPERATIONS = 512
|
|
42
|
+
--* @internal
|
|
43
|
+
local MAX_PENDING_PER_KEY = 32
|
|
44
|
+
--* @internal
|
|
45
|
+
local BUDGET_RESERVE_FRACTION = 0.2
|
|
46
|
+
--* @internal
|
|
47
|
+
local BUDGET_RESERVE_MIN = 12
|
|
48
|
+
--* @internal
|
|
49
|
+
local ORDINARY_AGING_SECONDS = 5
|
|
50
|
+
--* @internal
|
|
51
|
+
local RETRY_BASE_SECONDS = 0.5
|
|
52
|
+
--* @internal
|
|
53
|
+
local RETRY_CAP_SECONDS = 8
|
|
54
|
+
--* @internal
|
|
55
|
+
local KEY_WRITE_BYTES_PER_MINUTE = 3_145_728
|
|
56
|
+
--* @internal
|
|
57
|
+
local KEY_READ_BYTES_PER_MINUTE = 20_971_520
|
|
58
|
+
--* @internal The engine publishes no budget-change event, so blocked work rechecks at this period.
|
|
59
|
+
local BUDGET_RECHECK_SECONDS = 0.5
|
|
60
|
+
--* @internal Sizes in encoded JSON bytes (rule 39).
|
|
61
|
+
local MAX_ENVELOPE_BYTES = 1_048_576
|
|
62
|
+
--* @internal
|
|
63
|
+
local ENVELOPE_HEADER_MAX_BYTES = 4_096
|
|
64
|
+
--* @internal
|
|
65
|
+
local PAYLOAD_MAX_BYTES = 262_144
|
|
66
|
+
--* @internal
|
|
67
|
+
local RECORD_PAYLOAD_MAX_BYTES = 1_000_000
|
|
68
|
+
--* @internal
|
|
69
|
+
local OPERATIONS_MAX_BYTES = 18_432
|
|
70
|
+
--* @internal
|
|
71
|
+
local MAX_PURCHASE_IDS = 8_192
|
|
72
|
+
--* @internal
|
|
73
|
+
local RECEIPTS_MAX_BYTES = 393_216
|
|
74
|
+
--* @internal
|
|
75
|
+
local PURCHASE_ID_MAX_BYTES = 64
|
|
76
|
+
--* @internal
|
|
77
|
+
local INBOX_MAX_MESSAGES = 128
|
|
78
|
+
--* @internal
|
|
79
|
+
local INBOX_MAX_BYTES = 131_072
|
|
80
|
+
--* @internal
|
|
81
|
+
local QUARANTINE_MAX_MESSAGES = 8
|
|
82
|
+
--* @internal
|
|
83
|
+
local QUARANTINE_MAX_BYTES = 69_632
|
|
84
|
+
--* @internal
|
|
85
|
+
local MESSAGE_PAYLOAD_MAX_BYTES = 8_192
|
|
86
|
+
--* @internal Seven days (rule 69).
|
|
87
|
+
local MESSAGE_ID_MAX_AGE_SECONDS = 604_800
|
|
88
|
+
--* @internal
|
|
89
|
+
local ID_FUTURE_SKEW_SECONDS = 5
|
|
90
|
+
--* @internal The 30 s call deadline plus 5 s of clock error, with margin (rule 69).
|
|
91
|
+
local ID_EVICT_MIN_AGE_SECONDS = 60
|
|
92
|
+
--* @internal
|
|
93
|
+
local OPERATION_ID_MAX_BYTES = 64
|
|
94
|
+
--* @internal
|
|
95
|
+
local SEEN_MAX_MESSAGES = 1_024
|
|
96
|
+
--* @internal
|
|
97
|
+
local SEEN_MAX_BYTES = 69_632
|
|
98
|
+
--* @internal The wait after attempt n is entry n - 1; the last repeats (rule 73).
|
|
99
|
+
local INBOX_RETRY_SECONDS = { 5, 15, 60, 60 }
|
|
100
|
+
--* @internal Attempts before an item is quarantined with reason "attempts" (rule 73).
|
|
101
|
+
local INBOX_MAX_ATTEMPTS = 5
|
|
102
|
+
--* @internal
|
|
103
|
+
local OPERATION_MARKERS_PER_KEY = 256
|
|
104
|
+
--* @internal An operation id older than this is refused as expired (rule 88).
|
|
105
|
+
local OPERATION_ID_MAX_AGE_SECONDS = 3_600
|
|
106
|
+
--* @internal Roblox's DataStore name limit; a store name is its DataStore name (rule 2).
|
|
107
|
+
local STORE_NAME_MAX_BYTES = 50
|
|
108
|
+
--* @internal Data declarations per realm (rule 3).
|
|
109
|
+
local MAX_DATA_DECLARATIONS = 256
|
|
110
|
+
--* @internal A generic store's key is 1 to this many bytes (rule 86).
|
|
111
|
+
local KEY_MAX_BYTES = 50
|
|
112
|
+
--* @internal Profile stores per realm (rule 3).
|
|
113
|
+
local MAX_PROFILE_STORES = 4
|
|
114
|
+
--* @internal Networking's name limit, for profile message names (rule 2).
|
|
115
|
+
local MESSAGE_NAME_MAX_BYTES = 64
|
|
116
|
+
--* @internal A field view's name is this, the store name, ".", and the field (rules 2, 125, 134).
|
|
117
|
+
local VIEW_NAME_PREFIX = "Crucible.Data."
|
|
118
|
+
--* @internal The lifecycle view's name is this and the store name: at most 14 + 50 = 64 bytes (rules 2, 125, 134).
|
|
119
|
+
local LIVE_NAME_PREFIX = "Crucible.Live."
|
|
120
|
+
--* @internal Networking's MESSAGE_NAME_MAX_BYTES (rules 2, 134).
|
|
121
|
+
local VIEW_NAME_MAX_BYTES = 64
|
|
122
|
+
--* @internal Every profile store's views together: a third of each frame limit, divided per store at the seal (127).
|
|
123
|
+
local OWNER_VIEWS_MAX_BYTES = 65_536
|
|
124
|
+
--* @internal
|
|
125
|
+
local OWNER_VIEWS_MAX_ENTRIES = 4_000
|
|
126
|
+
--* @internal
|
|
127
|
+
local OWNER_VIEWS_MAX_VISITS = 16_000
|
|
128
|
+
--* @internal The handle members, reserved as template keys (rules 116, 130).
|
|
129
|
+
local RESERVED_HANDLE_NAMES = { "set", "update", "insert", "remove" }
|
|
130
|
+
--* @internal Persist admission (rules 14, 76).
|
|
131
|
+
local PERSIST_DEPTH = 28
|
|
132
|
+
--* @internal
|
|
133
|
+
local PERSIST_STRING_BYTES = 65_536
|
|
134
|
+
--* @internal
|
|
135
|
+
local PERSIST_ADMISSION_VISITS = 100_000
|
|
136
|
+
--* @internal Shutdown against core's sweep (rules 50 to 56).
|
|
137
|
+
local FINAL_PHASE_SECONDS = 10
|
|
138
|
+
--* @internal
|
|
139
|
+
local DISPATCH_CUTOFF_SECONDS = 1.5
|
|
140
|
+
--* @internal
|
|
141
|
+
local SETTLE_MARGIN_SECONDS = 0.5
|
|
142
|
+
--* @internal
|
|
143
|
+
local SHUTDOWN_FALLBACK_SECONDS = 2
|
|
144
|
+
--* @internal Kick messages (rules 20, 26, 47, 77).
|
|
145
|
+
local LOAD_BUSY_MESSAGE = "Your save is still open in another server. Please rejoin in a minute."
|
|
146
|
+
--* @internal
|
|
147
|
+
local LOAD_FAILED_MESSAGE = "Your data could not load. Please rejoin."
|
|
148
|
+
--* @internal
|
|
149
|
+
local SESSION_ENDED_MESSAGE = "Your data session ended. Please rejoin."
|
|
150
|
+
--* @internal
|
|
151
|
+
local SERVER_UPDATING_MESSAGE = "This server is updating to a newer version of the game. Please rejoin."
|
|
152
|
+
--* @internal Rule 39: both sums stay below MAX_ENVELOPE_BYTES.
|
|
153
|
+
local PROFILE_SECTIONS_BYTES = ENVELOPE_HEADER_MAX_BYTES + PAYLOAD_MAX_BYTES + RECEIPTS_MAX_BYTES + INBOX_MAX_BYTES + QUARANTINE_MAX_BYTES + SEEN_MAX_BYTES
|
|
154
|
+
--* @internal
|
|
155
|
+
local RECORD_SECTIONS_BYTES = ENVELOPE_HEADER_MAX_BYTES + RECORD_PAYLOAD_MAX_BYTES + OPERATIONS_MAX_BYTES
|
|
156
|
+
return {
|
|
157
|
+
LEASE_SECONDS = LEASE_SECONDS,
|
|
158
|
+
LOCAL_AUTHORITY_SECONDS = LOCAL_AUTHORITY_SECONDS,
|
|
159
|
+
RENEW_INTERVAL_SECONDS = RENEW_INTERVAL_SECONDS,
|
|
160
|
+
RENEW_JITTER_SECONDS = RENEW_JITTER_SECONDS,
|
|
161
|
+
TAKEOVER_GRACE_SECONDS = TAKEOVER_GRACE_SECONDS,
|
|
162
|
+
CLOCK_ERROR_ASSUMED_SECONDS = CLOCK_ERROR_ASSUMED_SECONDS,
|
|
163
|
+
LOAD_DEADLINE_SECONDS = LOAD_DEADLINE_SECONDS,
|
|
164
|
+
LOAD_POLL_SCHEDULE_SECONDS = LOAD_POLL_SCHEDULE_SECONDS,
|
|
165
|
+
LOAD_POLL_JITTER = LOAD_POLL_JITTER,
|
|
166
|
+
LOAD_POLL_MIN_SECONDS = LOAD_POLL_MIN_SECONDS,
|
|
167
|
+
LEASE_CRITICAL_SECONDS = LEASE_CRITICAL_SECONDS,
|
|
168
|
+
SAVE_TIMEOUT_DEFAULT_SECONDS = SAVE_TIMEOUT_DEFAULT_SECONDS,
|
|
169
|
+
SAVE_TIMEOUT_MAX_SECONDS = SAVE_TIMEOUT_MAX_SECONDS,
|
|
170
|
+
HANDOFF_TIMEOUT_SECONDS = HANDOFF_TIMEOUT_SECONDS,
|
|
171
|
+
RECEIPT_SAVE_TIMEOUT_SECONDS = RECEIPT_SAVE_TIMEOUT_SECONDS,
|
|
172
|
+
CALL_DEADLINE_SECONDS = CALL_DEADLINE_SECONDS,
|
|
173
|
+
MAX_IN_FLIGHT = MAX_IN_FLIGHT,
|
|
174
|
+
RESERVED_SESSION_SLOTS = RESERVED_SESSION_SLOTS,
|
|
175
|
+
ORDINARY_MAX_IN_FLIGHT = ORDINARY_MAX_IN_FLIGHT,
|
|
176
|
+
MAX_PENDING_OPERATIONS = MAX_PENDING_OPERATIONS,
|
|
177
|
+
MAX_PENDING_PER_KEY = MAX_PENDING_PER_KEY,
|
|
178
|
+
BUDGET_RESERVE_FRACTION = BUDGET_RESERVE_FRACTION,
|
|
179
|
+
BUDGET_RESERVE_MIN = BUDGET_RESERVE_MIN,
|
|
180
|
+
ORDINARY_AGING_SECONDS = ORDINARY_AGING_SECONDS,
|
|
181
|
+
RETRY_BASE_SECONDS = RETRY_BASE_SECONDS,
|
|
182
|
+
RETRY_CAP_SECONDS = RETRY_CAP_SECONDS,
|
|
183
|
+
KEY_WRITE_BYTES_PER_MINUTE = KEY_WRITE_BYTES_PER_MINUTE,
|
|
184
|
+
KEY_READ_BYTES_PER_MINUTE = KEY_READ_BYTES_PER_MINUTE,
|
|
185
|
+
BUDGET_RECHECK_SECONDS = BUDGET_RECHECK_SECONDS,
|
|
186
|
+
MAX_ENVELOPE_BYTES = MAX_ENVELOPE_BYTES,
|
|
187
|
+
ENVELOPE_HEADER_MAX_BYTES = ENVELOPE_HEADER_MAX_BYTES,
|
|
188
|
+
PAYLOAD_MAX_BYTES = PAYLOAD_MAX_BYTES,
|
|
189
|
+
RECORD_PAYLOAD_MAX_BYTES = RECORD_PAYLOAD_MAX_BYTES,
|
|
190
|
+
OPERATIONS_MAX_BYTES = OPERATIONS_MAX_BYTES,
|
|
191
|
+
MAX_PURCHASE_IDS = MAX_PURCHASE_IDS,
|
|
192
|
+
RECEIPTS_MAX_BYTES = RECEIPTS_MAX_BYTES,
|
|
193
|
+
PURCHASE_ID_MAX_BYTES = PURCHASE_ID_MAX_BYTES,
|
|
194
|
+
INBOX_MAX_MESSAGES = INBOX_MAX_MESSAGES,
|
|
195
|
+
INBOX_MAX_BYTES = INBOX_MAX_BYTES,
|
|
196
|
+
QUARANTINE_MAX_MESSAGES = QUARANTINE_MAX_MESSAGES,
|
|
197
|
+
QUARANTINE_MAX_BYTES = QUARANTINE_MAX_BYTES,
|
|
198
|
+
MESSAGE_PAYLOAD_MAX_BYTES = MESSAGE_PAYLOAD_MAX_BYTES,
|
|
199
|
+
MESSAGE_ID_MAX_AGE_SECONDS = MESSAGE_ID_MAX_AGE_SECONDS,
|
|
200
|
+
ID_FUTURE_SKEW_SECONDS = ID_FUTURE_SKEW_SECONDS,
|
|
201
|
+
ID_EVICT_MIN_AGE_SECONDS = ID_EVICT_MIN_AGE_SECONDS,
|
|
202
|
+
OPERATION_ID_MAX_BYTES = OPERATION_ID_MAX_BYTES,
|
|
203
|
+
SEEN_MAX_MESSAGES = SEEN_MAX_MESSAGES,
|
|
204
|
+
SEEN_MAX_BYTES = SEEN_MAX_BYTES,
|
|
205
|
+
INBOX_RETRY_SECONDS = INBOX_RETRY_SECONDS,
|
|
206
|
+
INBOX_MAX_ATTEMPTS = INBOX_MAX_ATTEMPTS,
|
|
207
|
+
OPERATION_MARKERS_PER_KEY = OPERATION_MARKERS_PER_KEY,
|
|
208
|
+
OPERATION_ID_MAX_AGE_SECONDS = OPERATION_ID_MAX_AGE_SECONDS,
|
|
209
|
+
STORE_NAME_MAX_BYTES = STORE_NAME_MAX_BYTES,
|
|
210
|
+
MAX_DATA_DECLARATIONS = MAX_DATA_DECLARATIONS,
|
|
211
|
+
KEY_MAX_BYTES = KEY_MAX_BYTES,
|
|
212
|
+
MAX_PROFILE_STORES = MAX_PROFILE_STORES,
|
|
213
|
+
MESSAGE_NAME_MAX_BYTES = MESSAGE_NAME_MAX_BYTES,
|
|
214
|
+
VIEW_NAME_PREFIX = VIEW_NAME_PREFIX,
|
|
215
|
+
LIVE_NAME_PREFIX = LIVE_NAME_PREFIX,
|
|
216
|
+
VIEW_NAME_MAX_BYTES = VIEW_NAME_MAX_BYTES,
|
|
217
|
+
OWNER_VIEWS_MAX_BYTES = OWNER_VIEWS_MAX_BYTES,
|
|
218
|
+
OWNER_VIEWS_MAX_ENTRIES = OWNER_VIEWS_MAX_ENTRIES,
|
|
219
|
+
OWNER_VIEWS_MAX_VISITS = OWNER_VIEWS_MAX_VISITS,
|
|
220
|
+
RESERVED_HANDLE_NAMES = RESERVED_HANDLE_NAMES,
|
|
221
|
+
PERSIST_DEPTH = PERSIST_DEPTH,
|
|
222
|
+
PERSIST_STRING_BYTES = PERSIST_STRING_BYTES,
|
|
223
|
+
PERSIST_ADMISSION_VISITS = PERSIST_ADMISSION_VISITS,
|
|
224
|
+
FINAL_PHASE_SECONDS = FINAL_PHASE_SECONDS,
|
|
225
|
+
DISPATCH_CUTOFF_SECONDS = DISPATCH_CUTOFF_SECONDS,
|
|
226
|
+
SETTLE_MARGIN_SECONDS = SETTLE_MARGIN_SECONDS,
|
|
227
|
+
SHUTDOWN_FALLBACK_SECONDS = SHUTDOWN_FALLBACK_SECONDS,
|
|
228
|
+
LOAD_BUSY_MESSAGE = LOAD_BUSY_MESSAGE,
|
|
229
|
+
LOAD_FAILED_MESSAGE = LOAD_FAILED_MESSAGE,
|
|
230
|
+
SESSION_ENDED_MESSAGE = SESSION_ENDED_MESSAGE,
|
|
231
|
+
SERVER_UPDATING_MESSAGE = SERVER_UPDATING_MESSAGE,
|
|
232
|
+
PROFILE_SECTIONS_BYTES = PROFILE_SECTIONS_BYTES,
|
|
233
|
+
RECORD_SECTIONS_BYTES = RECORD_SECTIONS_BYTES,
|
|
234
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local admit = TS.import(script, script.Parent, "admission").admit
|
|
4
|
+
local MESSAGE_NAME_MAX_BYTES = TS.import(script, script.Parent, "limits").MESSAGE_NAME_MAX_BYTES
|
|
5
|
+
local dataRegistrySealed = TS.import(script, script.Parent, "record-declarations").dataRegistrySealed
|
|
6
|
+
local byName = {}
|
|
7
|
+
local byToken = setmetatable({}, {
|
|
8
|
+
__mode = "k",
|
|
9
|
+
})
|
|
10
|
+
--[[
|
|
11
|
+
*
|
|
12
|
+
* @internal The emitted `profileMessage` (rules 2, 3, 65): the name re-checked, the payload admitted by rule 76 against
|
|
13
|
+
* its descriptor, so a newer sender's added optional field stays in place unread. Shared-safe.
|
|
14
|
+
|
|
15
|
+
]]
|
|
16
|
+
local function declareProfileMessage(name, payload)
|
|
17
|
+
if #name == 0 or #name > MESSAGE_NAME_MAX_BYTES then
|
|
18
|
+
error(`{name}: a profile message name is 1 to {MESSAGE_NAME_MAX_BYTES} bytes`, 3)
|
|
19
|
+
end
|
|
20
|
+
local segments = string.split(name, ".")
|
|
21
|
+
for _, segment in segments do
|
|
22
|
+
if (string.match(segment, "^[A-Za-z][A-Za-z0-9_]*$")) == nil then
|
|
23
|
+
error(`{name}: a profile message name is dot-separated segments of letters, digits and underscores`, 3)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
if segments[1] == "Crucible" then
|
|
27
|
+
error(`{name}: the name Crucible is reserved`, 3)
|
|
28
|
+
end
|
|
29
|
+
if dataRegistrySealed() then
|
|
30
|
+
error(`{name} was declared after the data registry sealed; declare messages in modules that boot loads`, 3)
|
|
31
|
+
end
|
|
32
|
+
local _name = name
|
|
33
|
+
if byName[_name] ~= nil then
|
|
34
|
+
error(`{name} is already declared`, 3)
|
|
35
|
+
end
|
|
36
|
+
local declaration = {
|
|
37
|
+
name = name,
|
|
38
|
+
admit = function(value)
|
|
39
|
+
local result = admit(payload, value)
|
|
40
|
+
return if result.ok then {
|
|
41
|
+
ok = true,
|
|
42
|
+
value = result.value,
|
|
43
|
+
} else {
|
|
44
|
+
ok = false,
|
|
45
|
+
}
|
|
46
|
+
end,
|
|
47
|
+
}
|
|
48
|
+
local token = table.freeze({})
|
|
49
|
+
local _name_1 = name
|
|
50
|
+
byName[_name_1] = declaration
|
|
51
|
+
byToken[token] = declaration
|
|
52
|
+
return token
|
|
53
|
+
end
|
|
54
|
+
--* @internal The declaration behind a `ProfileMessage` value, or undefined for anything else.
|
|
55
|
+
local function messageOf(token)
|
|
56
|
+
local _token = token
|
|
57
|
+
local _result
|
|
58
|
+
if type(_token) == "table" then
|
|
59
|
+
local _token_1 = token
|
|
60
|
+
_result = byToken[_token_1]
|
|
61
|
+
else
|
|
62
|
+
_result = nil
|
|
63
|
+
end
|
|
64
|
+
return _result
|
|
65
|
+
end
|
|
66
|
+
return {
|
|
67
|
+
declareProfileMessage = declareProfileMessage,
|
|
68
|
+
messageOf = messageOf,
|
|
69
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local _limits = TS.import(script, script.Parent, "limits")
|
|
4
|
+
local ID_EVICT_MIN_AGE_SECONDS = _limits.ID_EVICT_MIN_AGE_SECONDS
|
|
5
|
+
local ID_FUTURE_SKEW_SECONDS = _limits.ID_FUTURE_SKEW_SECONDS
|
|
6
|
+
local DIGITS = "0123456789abcdefghijklmnopqrstuvwxyz"
|
|
7
|
+
local function base36(value)
|
|
8
|
+
local rest = math.floor(value)
|
|
9
|
+
local text = ""
|
|
10
|
+
repeat
|
|
11
|
+
do
|
|
12
|
+
local digit = rest % 36
|
|
13
|
+
local _arg0 = digit + 1
|
|
14
|
+
local _arg1 = digit + 1
|
|
15
|
+
text = string.sub(DIGITS, _arg0, _arg1) .. text
|
|
16
|
+
rest = math.floor(rest / 36)
|
|
17
|
+
end
|
|
18
|
+
until not (rest > 0)
|
|
19
|
+
return text
|
|
20
|
+
end
|
|
21
|
+
--[[
|
|
22
|
+
*
|
|
23
|
+
* @internal An operation id (rules 69, 88): "o1-", the creation second in base 36, "-", and the platform's GUID without
|
|
24
|
+
* its dashes. It carries its own creation time, so the age and floor checks need no stored record.
|
|
25
|
+
|
|
26
|
+
]]
|
|
27
|
+
local function newOperationId(port)
|
|
28
|
+
local guid = string.gsub(string.lower(port.guid()), "[^%w]", "")
|
|
29
|
+
return `o1-{base36(port.wall())}-{guid}`
|
|
30
|
+
end
|
|
31
|
+
--* @internal The creation second of an id of this format, and undefined for any other text.
|
|
32
|
+
local function createdAt(id)
|
|
33
|
+
local seconds = string.match(id, "^o1%-([0-9a-z]+)%-[0-9a-z]+$")
|
|
34
|
+
return if seconds == nil then nil else tonumber(seconds, 36)
|
|
35
|
+
end
|
|
36
|
+
--* @internal
|
|
37
|
+
--* @internal Rule 69's horizon: at most `maxAgeSeconds` old and at most ID_FUTURE_SKEW_SECONDS ahead of `now`.
|
|
38
|
+
local function ageOf(id, now, maxAgeSeconds)
|
|
39
|
+
local created = createdAt(id)
|
|
40
|
+
if created == nil then
|
|
41
|
+
return "malformed"
|
|
42
|
+
end
|
|
43
|
+
if created > now + ID_FUTURE_SKEW_SECONDS then
|
|
44
|
+
return "future"
|
|
45
|
+
end
|
|
46
|
+
if now - created > maxAgeSeconds then
|
|
47
|
+
return "expired"
|
|
48
|
+
end
|
|
49
|
+
return "fresh"
|
|
50
|
+
end
|
|
51
|
+
--* @internal Whether a ring may evict this id at `now`: only once it is older than ID_EVICT_MIN_AGE_SECONDS (rule 69).
|
|
52
|
+
local function evictable(id, now)
|
|
53
|
+
local created = createdAt(id)
|
|
54
|
+
return created == nil or now - created > ID_EVICT_MIN_AGE_SECONDS
|
|
55
|
+
end
|
|
56
|
+
return {
|
|
57
|
+
newOperationId = newOperationId,
|
|
58
|
+
createdAt = createdAt,
|
|
59
|
+
ageOf = ageOf,
|
|
60
|
+
evictable = evictable,
|
|
61
|
+
}
|