@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.
Files changed (134) hide show
  1. package/out/client/index.d.ts +7 -0
  2. package/out/client/init.luau +36 -1
  3. package/out/configs.d.ts +37 -0
  4. package/out/configs.luau +46 -0
  5. package/out/data.d.ts +224 -0
  6. package/out/data.luau +97 -0
  7. package/out/emit/configs.d.ts +4 -0
  8. package/out/emit/configs.luau +22 -0
  9. package/out/emit/data.d.ts +6 -0
  10. package/out/emit/data.luau +19 -0
  11. package/out/emit/index.d.ts +5 -1
  12. package/out/emit/init.luau +23 -7
  13. package/out/emit/network.d.ts +6 -0
  14. package/out/emit/network.luau +36 -0
  15. package/out/emit/replicated.d.ts +4 -0
  16. package/out/emit/replicated.luau +13 -0
  17. package/out/index.d.ts +8 -0
  18. package/out/init.luau +23 -0
  19. package/out/internal/binding.luau +5 -0
  20. package/out/internal/boot.luau +107 -8
  21. package/out/internal/configs/limits.d.ts +10 -0
  22. package/out/internal/configs/limits.luau +25 -0
  23. package/out/internal/configs/port.d.ts +29 -0
  24. package/out/internal/configs/port.luau +99 -0
  25. package/out/internal/configs/runtime.d.ts +21 -0
  26. package/out/internal/configs/runtime.luau +981 -0
  27. package/out/internal/data/admission.d.ts +1 -0
  28. package/out/internal/data/admission.luau +673 -0
  29. package/out/internal/data/declarations.d.ts +1 -0
  30. package/out/internal/data/declarations.luau +384 -0
  31. package/out/internal/data/envelope.d.ts +1 -0
  32. package/out/internal/data/envelope.luau +318 -0
  33. package/out/internal/data/handles.d.ts +1 -0
  34. package/out/internal/data/handles.luau +255 -0
  35. package/out/internal/data/inbox-transforms.d.ts +1 -0
  36. package/out/internal/data/inbox-transforms.luau +458 -0
  37. package/out/internal/data/inbox.d.ts +1 -0
  38. package/out/internal/data/inbox.luau +437 -0
  39. package/out/internal/data/lease.d.ts +1 -0
  40. package/out/internal/data/lease.luau +304 -0
  41. package/out/internal/data/limits.d.ts +1 -0
  42. package/out/internal/data/limits.luau +234 -0
  43. package/out/internal/data/messages.d.ts +1 -0
  44. package/out/internal/data/messages.luau +69 -0
  45. package/out/internal/data/operation-id.d.ts +1 -0
  46. package/out/internal/data/operation-id.luau +61 -0
  47. package/out/internal/data/paths.d.ts +2 -0
  48. package/out/internal/data/paths.luau +323 -0
  49. package/out/internal/data/port.d.ts +1 -0
  50. package/out/internal/data/port.luau +290 -0
  51. package/out/internal/data/profiles.d.ts +1 -0
  52. package/out/internal/data/profiles.luau +862 -0
  53. package/out/internal/data/receipts.d.ts +1 -0
  54. package/out/internal/data/receipts.luau +190 -0
  55. package/out/internal/data/record-bindings.d.ts +1 -0
  56. package/out/internal/data/record-bindings.luau +92 -0
  57. package/out/internal/data/record-declarations.d.ts +1 -0
  58. package/out/internal/data/record-declarations.luau +140 -0
  59. package/out/internal/data/records.d.ts +1 -0
  60. package/out/internal/data/records.luau +400 -0
  61. package/out/internal/data/runtime.d.ts +1 -0
  62. package/out/internal/data/runtime.luau +918 -0
  63. package/out/internal/data/scheduler.d.ts +1 -0
  64. package/out/internal/data/scheduler.luau +507 -0
  65. package/out/internal/data/session.d.ts +1 -0
  66. package/out/internal/data/session.luau +234 -0
  67. package/out/internal/data/shutdown.d.ts +1 -0
  68. package/out/internal/data/shutdown.luau +91 -0
  69. package/out/internal/data/waiters.d.ts +1 -0
  70. package/out/internal/data/waiters.luau +65 -0
  71. package/out/internal/lifecycle.luau +45 -0
  72. package/out/internal/network/catalog.d.ts +26 -0
  73. package/out/internal/network/catalog.luau +182 -0
  74. package/out/internal/network/claims.d.ts +1 -0
  75. package/out/internal/network/claims.luau +154 -0
  76. package/out/internal/network/client-session.d.ts +1 -0
  77. package/out/internal/network/client-session.luau +403 -0
  78. package/out/internal/network/codec.d.ts +63 -0
  79. package/out/internal/network/codec.luau +1183 -0
  80. package/out/internal/network/envelope.d.ts +1 -0
  81. package/out/internal/network/envelope.luau +247 -0
  82. package/out/internal/network/limits.d.ts +26 -0
  83. package/out/internal/network/limits.luau +55 -0
  84. package/out/internal/network/port.d.ts +1 -0
  85. package/out/internal/network/port.luau +19 -0
  86. package/out/internal/network/roblox-network-port.d.ts +1 -0
  87. package/out/internal/network/roblox-network-port.luau +105 -0
  88. package/out/internal/network/schema.d.ts +72 -0
  89. package/out/internal/network/schema.luau +386 -0
  90. package/out/internal/network/server-session.d.ts +1 -0
  91. package/out/internal/network/server-session.luau +393 -0
  92. package/out/internal/network/session.d.ts +1 -0
  93. package/out/internal/network/session.luau +266 -0
  94. package/out/internal/reactive/context.d.ts +1 -0
  95. package/out/internal/reactive/context.luau +149 -0
  96. package/out/internal/reactive/flush.d.ts +1 -0
  97. package/out/internal/reactive/flush.luau +355 -0
  98. package/out/internal/reactive/graph.d.ts +1 -0
  99. package/out/internal/reactive/graph.luau +626 -0
  100. package/out/internal/reactive/limits.d.ts +1 -0
  101. package/out/internal/reactive/limits.luau +54 -0
  102. package/out/internal/reactive/ownership.d.ts +1 -0
  103. package/out/internal/reactive/ownership.luau +626 -0
  104. package/out/internal/registry.luau +10 -1
  105. package/out/internal/sync/client-sync.d.ts +1 -0
  106. package/out/internal/sync/client-sync.luau +157 -0
  107. package/out/internal/sync/declarations.d.ts +1 -0
  108. package/out/internal/sync/declarations.luau +276 -0
  109. package/out/internal/sync/frame.d.ts +1 -0
  110. package/out/internal/sync/frame.luau +91 -0
  111. package/out/internal/sync/server-sync.d.ts +1 -0
  112. package/out/internal/sync/server-sync.luau +502 -0
  113. package/out/internal/sync/values.d.ts +1 -0
  114. package/out/internal/sync/values.luau +930 -0
  115. package/out/network.d.ts +66 -0
  116. package/out/network.luau +24 -0
  117. package/out/reactive.d.ts +76 -0
  118. package/out/reactive.luau +136 -0
  119. package/out/replicated.d.ts +38 -0
  120. package/out/replicated.luau +45 -0
  121. package/out/server/data.d.ts +54 -0
  122. package/out/server/data.luau +44 -0
  123. package/out/server/index.d.ts +16 -0
  124. package/out/server/init.luau +49 -3
  125. package/out/server/publish.d.ts +20 -0
  126. package/out/server/publish.luau +24 -0
  127. package/out/vide/index.d.ts +7 -0
  128. package/out/vide/init.luau +50 -0
  129. package/package.json +13 -2
  130. package/transformer/data.cjs +72 -0
  131. package/transformer/declarations.cjs +153 -0
  132. package/transformer/index.js +37 -11
  133. package/transformer/network.cjs +961 -0
  134. 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
+ }
@@ -0,0 +1,2 @@
1
+ /** `{ "career", "a.b" }`, as rule 131's messages print a path. */
2
+ export declare function showPath(path: readonly string[]): string;