@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
package/out/index.d.ts CHANGED
@@ -10,3 +10,11 @@ export interface BootOptions {
10
10
  }
11
11
  export { Logging, LogLevel } from "./logging";
12
12
  export type { Logger, LogRecord, LogSink, SinkSubscription, LogContext, LogProperties } from "./logging";
13
+ export { state, computed, effect, watch, untracked, scope, onCleanup, own } from "./reactive";
14
+ export type { Readable, State, Ownable } from "./reactive";
15
+ export { replicated, replicatedPerPlayer, syncStatus } from "./replicated";
16
+ export type { Replicated, ReplicatedPerPlayer, SyncStatus } from "./replicated";
17
+ export { toServer, toClient, askServer, receive, ClientReady } from "./network";
18
+ export type { ToServer, ToClient, AskServer, Receiver, ReceiveTarget, ReceiveCallback, AskResult, AskFailure, AskOptions, PayloadArgs, f32, f64, u8, u16, u32, } from "./network";
19
+ export { profileStore, serverOnly, replicatedPaths } from "./data";
20
+ export type { ProfileStore, ProfileOptions, ServerOnly, ProfileData, ClientData, Profile, ClientProfile, DataHandle, ReadHandle, WriteHandle, ArrayWrites, ReservedName, Json, JsonRecord, Migration, } from "./data";
package/out/init.luau CHANGED
@@ -8,4 +8,27 @@ exports.CrucibleBehaviour = TS.import(script, script, "crucible-behaviour").Cruc
8
8
  local _logging = TS.import(script, script, "logging")
9
9
  exports.Logging = _logging.Logging
10
10
  exports.LogLevel = _logging.LogLevel
11
+ local _reactive = TS.import(script, script, "reactive")
12
+ exports.state = _reactive.state
13
+ exports.computed = _reactive.computed
14
+ exports.effect = _reactive.effect
15
+ exports.watch = _reactive.watch
16
+ exports.untracked = _reactive.untracked
17
+ exports.scope = _reactive.scope
18
+ exports.onCleanup = _reactive.onCleanup
19
+ exports.own = _reactive.own
20
+ local _replicated = TS.import(script, script, "replicated")
21
+ exports.replicated = _replicated.replicated
22
+ exports.replicatedPerPlayer = _replicated.replicatedPerPlayer
23
+ exports.syncStatus = _replicated.syncStatus
24
+ local _network = TS.import(script, script, "network")
25
+ exports.toServer = _network.toServer
26
+ exports.toClient = _network.toClient
27
+ exports.askServer = _network.askServer
28
+ exports.receive = _network.receive
29
+ exports.ClientReady = _network.ClientReady
30
+ local _data = TS.import(script, script, "data")
31
+ exports.profileStore = _data.profileStore
32
+ exports.serverOnly = _data.serverOnly
33
+ exports.replicatedPaths = _data.replicatedPaths
11
34
  return exports
@@ -27,6 +27,10 @@ local scopeTarget
27
27
  local function port()
28
28
  return installed or error("Crucible binding used before boot")
29
29
  end
30
+ --* @internal The port boot installed, for framework Subsystems that connect engine events.
31
+ local function enginePort()
32
+ return port()
33
+ end
30
34
  --* @internal
31
35
  local function useEngine(engine)
32
36
  installed = engine
@@ -597,6 +601,7 @@ local function forget(state)
597
601
  end
598
602
  end
599
603
  return {
604
+ enginePort = enginePort,
600
605
  useEngine = useEngine,
601
606
  defaultAccepts = defaultAccepts,
602
607
  construct = construct,
@@ -26,6 +26,14 @@ local records = _registry.records
26
26
  local seal = _registry.seal
27
27
  local robloxPort = TS.import(script, script.Parent, "roblox-port").robloxPort
28
28
  local Scheduler = TS.import(script, script.Parent, "scheduler").Scheduler
29
+ local _flush = TS.import(script, script.Parent, "reactive", "flush")
30
+ local holdBarrier = _flush.holdBarrier
31
+ local releaseBarrier = _flush.releaseBarrier
32
+ local useReactivePort = _flush.useReactivePort
33
+ local endInterval = TS.import(script, script.Parent, "reactive", "ownership").endInterval
34
+ local _declarations = TS.import(script, script.Parent, "sync", "declarations")
35
+ local rollbackDeclarations = _declarations.rollbackDeclarations
36
+ local sealDeclarations = _declarations.sealDeclarations
29
37
  --* @internal
30
38
  local BOOT_CLASSES = { Subsystem, CrucibleBehaviour }
31
39
  --* @internal
@@ -35,6 +43,45 @@ local PATTERN_PREFIX = "crucible-path:"
35
43
  local SHUTDOWN_DEADLINE_SECONDS = 25
36
44
  --* @internal
37
45
  local SHUTDOWN_TIMEOUT_SECONDS = 28
46
+ local clock
47
+ local sweep
48
+ --* @internal
49
+ --* @internal The one monotonic clock, core's and data's (data rules 25, 105).
50
+ local function bootClock()
51
+ return (clock or os.clock)()
52
+ end
53
+ --* @internal Undefined until the sweep has begun.
54
+ local function shutdownProgress(subsystem)
55
+ if sweep == nil then
56
+ return nil
57
+ end
58
+ local pending = 0
59
+ local notStarted = 0
60
+ for _, record in sweep.order do
61
+ local _condition = sweep.finished[record] ~= nil
62
+ if not _condition then
63
+ local _result = record.state
64
+ if _result ~= nil then
65
+ _result = _result.object
66
+ end
67
+ _condition = _result == subsystem
68
+ end
69
+ if _condition then
70
+ continue
71
+ end
72
+ pending += 1
73
+ if not (sweep.started[record] ~= nil) then
74
+ notStarted += 1
75
+ end
76
+ end
77
+ return {
78
+ startedAt = sweep.startTime,
79
+ forceStartAt = sweep.startTime + SHUTDOWN_DEADLINE_SECONDS,
80
+ deadline = sweep.startTime + SHUTDOWN_TIMEOUT_SECONDS,
81
+ pending = pending,
82
+ notStarted = notStarted,
83
+ }
84
+ end
38
85
  --* @internal
39
86
  local function parsePattern(pattern)
40
87
  local _pattern = pattern
@@ -214,6 +261,12 @@ local function shutdown(port, order)
214
261
  end
215
262
  end
216
263
  local startTime = port.clock()
264
+ sweep = {
265
+ startTime = startTime,
266
+ order = order,
267
+ started = started,
268
+ finished = finished,
269
+ }
217
270
  while true do
218
271
  -- ▼ ReadonlySet.size ▼
219
272
  local _size = 0
@@ -287,6 +340,8 @@ local function shutdown(port, order)
287
340
  end
288
341
  --* @internal
289
342
  local function ignite(port, side, options)
343
+ clock = port.clock
344
+ sweep = nil
290
345
  local singletons = seal({
291
346
  renders = side.renders,
292
347
  })
@@ -330,6 +385,8 @@ local function ignite(port, side, options)
330
385
  end
331
386
  end
332
387
  useEngine(port)
388
+ holdBarrier()
389
+ useReactivePort(port)
333
390
  useImplementerPort(port)
334
391
  useEffects({
335
392
  construct = function(s)
@@ -391,6 +448,7 @@ local function ignite(port, side, options)
391
448
  diagnose = function(s, hook, err)
392
449
  return runtimeDiagnostic("error", `{where(port, s)} {HOOK_NAMES[hook + 1]} failed: {tostring(err)}`)
393
450
  end,
451
+ disabled = endInterval,
394
452
  })
395
453
  local ok, err = pcall(function()
396
454
  local _exp_1 = records()
@@ -427,6 +485,36 @@ local function ignite(port, side, options)
427
485
  if not ok then
428
486
  reject(tostring(err))
429
487
  end
488
+ if failure == nil then
489
+ local unbooted = nil
490
+ local finalized, finalizeErr = pcall(function()
491
+ -- Roblox cannot unbind BindToClose, so the callback checks that this boot committed.
492
+ if side.name == "server" then
493
+ port.bindToClose(function()
494
+ if stage ~= "committed" then
495
+ return nil
496
+ end
497
+ shutdown(port, singletons)
498
+ side.network:close()
499
+ end)
500
+ end
501
+ unbooted = side.booted()
502
+ if side.name == "client" then
503
+ sealDeclarations()
504
+ end
505
+ side.network:commit()
506
+ end)
507
+ if not finalized then
508
+ local undo = unbooted
509
+ if undo ~= nil then
510
+ local undone, undoErr = pcall(undo)
511
+ if not undone then
512
+ runtimeDiagnostic("error", `Crucible: undoing booted failed: {tostring(undoErr)}`)
513
+ end
514
+ end
515
+ reject(`boot finalization failed: {tostring(finalizeErr)}`)
516
+ end
517
+ end
430
518
  if failure ~= nil then
431
519
  for s in owned do
432
520
  if not hasEnded(s) then
@@ -435,23 +523,34 @@ local function ignite(port, side, options)
435
523
  end
436
524
  drain()
437
525
  table.clear(owned)
526
+ releaseBarrier()
438
527
  error(`Crucible boot failed: {failure}`, 0)
439
528
  end
440
529
  stage = "committed"
441
530
  table.clear(owned)
442
- if side.name == "server" then
443
- port.bindToClose(function()
444
- return shutdown(port, singletons)
445
- end)
446
- end
447
- side.booted()
531
+ releaseBarrier()
448
532
  end
449
533
  --* @internal
450
534
  local function boot(side, options)
451
- load(options.load)
452
- ignite(robloxPort(), side, options)
535
+ local ok, err = pcall(function()
536
+ side.network:open()
537
+ load(options.load)
538
+ side.network:loaded()
539
+ if side.name == "server" then
540
+ sealDeclarations()
541
+ end
542
+ ignite(robloxPort(), side, options)
543
+ end)
544
+ if ok then
545
+ return nil
546
+ end
547
+ side.network:close()
548
+ rollbackDeclarations()
549
+ error(err, 0)
453
550
  end
454
551
  return {
552
+ bootClock = bootClock,
553
+ shutdownProgress = shutdownProgress,
455
554
  parsePattern = parsePattern,
456
555
  load = load,
457
556
  ignite = ignite,
@@ -0,0 +1,10 @@
1
+ export declare const MAX_CONFIG_DECLARATIONS = 1000;
2
+ export declare const CONFIG_NAME_MAX_BYTES = 64;
3
+ export declare const CONFIG_VALUE_MAX_BYTES = 100000;
4
+ export declare const CONFIG_ADMISSION_VISITS = 100000;
5
+ export declare const CONFIG_REQUEST_CONCURRENCY = 4;
6
+ export declare const CONFIG_FETCH_RETRY_BASE_SECONDS = 1;
7
+ export declare const CONFIG_FETCH_RETRY_CAP_SECONDS = 30;
8
+ export declare const CONFIG_REFRESH_TIMEOUT_SECONDS = 10;
9
+ export declare const CONFIG_VALUE_DEPTH = 28;
10
+ export declare const CONFIG_STRING_MAX_BYTES = 65536;
@@ -0,0 +1,25 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local MAX_CONFIG_DECLARATIONS = 1_000
3
+ local CONFIG_NAME_MAX_BYTES = 64
4
+ local CONFIG_VALUE_MAX_BYTES = 100_000
5
+ local CONFIG_ADMISSION_VISITS = 100_000
6
+ local CONFIG_REQUEST_CONCURRENCY = 4
7
+ local CONFIG_FETCH_RETRY_BASE_SECONDS = 1
8
+ local CONFIG_FETCH_RETRY_CAP_SECONDS = 30
9
+ local CONFIG_REFRESH_TIMEOUT_SECONDS = 10
10
+ -- Rule 97 admits configs by rule 76's walk, whose depth and string bounds are data's PERSIST_DEPTH and
11
+ -- PERSIST_STRING_BYTES. Data's limits module is not under configs yet, so the values are restated here.
12
+ local CONFIG_VALUE_DEPTH = 28
13
+ local CONFIG_STRING_MAX_BYTES = 65_536
14
+ return {
15
+ MAX_CONFIG_DECLARATIONS = MAX_CONFIG_DECLARATIONS,
16
+ CONFIG_NAME_MAX_BYTES = CONFIG_NAME_MAX_BYTES,
17
+ CONFIG_VALUE_MAX_BYTES = CONFIG_VALUE_MAX_BYTES,
18
+ CONFIG_ADMISSION_VISITS = CONFIG_ADMISSION_VISITS,
19
+ CONFIG_REQUEST_CONCURRENCY = CONFIG_REQUEST_CONCURRENCY,
20
+ CONFIG_FETCH_RETRY_BASE_SECONDS = CONFIG_FETCH_RETRY_BASE_SECONDS,
21
+ CONFIG_FETCH_RETRY_CAP_SECONDS = CONFIG_FETCH_RETRY_CAP_SECONDS,
22
+ CONFIG_REFRESH_TIMEOUT_SECONDS = CONFIG_REFRESH_TIMEOUT_SECONDS,
23
+ CONFIG_VALUE_DEPTH = CONFIG_VALUE_DEPTH,
24
+ CONFIG_STRING_MAX_BYTES = CONFIG_STRING_MAX_BYTES,
25
+ }
@@ -0,0 +1,29 @@
1
+ import type { Disconnect } from "../port";
2
+ export interface ConfigSnapshotPort {
3
+ /** `GetValue`: nil for a missing key. A JSON config may arrive as a table or as its text. */
4
+ readonly get: (key: string) => unknown;
5
+ /** `Refresh`. Yields and may throw. */
6
+ readonly refresh: () => void;
7
+ /** Unmeasured in Studio: whether this turns true without `UpdateAvailable`. Runtime treats the two as independent. */
8
+ readonly outdated: () => boolean;
9
+ /** `Error` is `LoadFailed`. */
10
+ readonly failed: () => boolean;
11
+ readonly onUpdateAvailable: (listener: () => void) => Disconnect;
12
+ }
13
+ /** What configs ask of the engine. Requests yield and may throw. */
14
+ export interface ConfigPort {
15
+ readonly global: () => ConfigSnapshotPort;
16
+ readonly forPlayer: (player: Player) => ConfigSnapshotPort;
17
+ readonly players: () => readonly Player[];
18
+ readonly onPlayerAdded: (listener: (player: Player) => void) => Disconnect;
19
+ readonly onPlayerRemoving: (listener: (player: Player) => void) => Disconnect;
20
+ readonly wait: (seconds: number) => void;
21
+ /** Uniform in [0, 1). */
22
+ readonly random: () => number;
23
+ readonly decodeJson: (text: string) => unknown;
24
+ /** The JSON text length of a decoded value. Throws for a value JSON cannot hold. */
25
+ readonly jsonBytes: (value: object) => number;
26
+ }
27
+ export declare function installConfigPort(value: ConfigPort): void;
28
+ export declare function configPort(): ConfigPort;
29
+ export declare function createRobloxConfigPort(): ConfigPort;
@@ -0,0 +1,99 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ --* What configs ask of the engine. Requests yield and may throw.
3
+ local port
4
+ local function installConfigPort(value)
5
+ local _arg0 = port == nil
6
+ assert(_arg0, "Config port already selected")
7
+ port = value
8
+ end
9
+ local createRobloxConfigPort
10
+ local function configPort()
11
+ if port == nil then
12
+ port = createRobloxConfigPort()
13
+ end
14
+ return port
15
+ end
16
+ local EMPTY = table.freeze({
17
+ get = function()
18
+ return nil
19
+ end,
20
+ refresh = function() end,
21
+ outdated = function()
22
+ return false
23
+ end,
24
+ failed = function()
25
+ return false
26
+ end,
27
+ onUpdateAvailable = function()
28
+ return function() end
29
+ end,
30
+ })
31
+ local function snapshotPort(snapshot)
32
+ return {
33
+ get = function(key)
34
+ return snapshot:GetValue(key)
35
+ end,
36
+ refresh = function()
37
+ return snapshot:Refresh()
38
+ end,
39
+ outdated = function()
40
+ return snapshot.Outdated
41
+ end,
42
+ failed = function()
43
+ return snapshot.Error == Enum.ConfigSnapshotErrorState.LoadFailed
44
+ end,
45
+ onUpdateAvailable = function(listener)
46
+ local connection = snapshot.UpdateAvailable:Connect(listener)
47
+ return function()
48
+ return connection:Disconnect()
49
+ end
50
+ end,
51
+ }
52
+ end
53
+ function createRobloxConfigPort()
54
+ local players = game:GetService("Players")
55
+ local http = game:GetService("HttpService")
56
+ local live = not game:GetService("RunService"):IsStudio() or game:GetService("ServerStorage"):GetAttribute("CrucibleLiveData") == true
57
+ local service = if live then game:GetService("ConfigService") else nil
58
+ local random = Random.new()
59
+ return {
60
+ global = function()
61
+ return if service == nil then EMPTY else snapshotPort(service:GetConfigAsync())
62
+ end,
63
+ forPlayer = function(player)
64
+ return if service == nil then EMPTY else snapshotPort(service:GetConfigForPlayerAsync(player))
65
+ end,
66
+ players = function()
67
+ return players:GetPlayers()
68
+ end,
69
+ onPlayerAdded = function(listener)
70
+ local connection = players.PlayerAdded:Connect(listener)
71
+ return function()
72
+ return connection:Disconnect()
73
+ end
74
+ end,
75
+ onPlayerRemoving = function(listener)
76
+ local connection = players.PlayerRemoving:Connect(listener)
77
+ return function()
78
+ return connection:Disconnect()
79
+ end
80
+ end,
81
+ wait = function(seconds)
82
+ task.wait(seconds)
83
+ end,
84
+ random = function()
85
+ return random:NextNumber()
86
+ end,
87
+ decodeJson = function(text)
88
+ return http:JSONDecode(text)
89
+ end,
90
+ jsonBytes = function(value)
91
+ return #http:JSONEncode(value)
92
+ end,
93
+ }
94
+ end
95
+ return {
96
+ installConfigPort = installConfigPort,
97
+ configPort = configPort,
98
+ createRobloxConfigPort = createRobloxConfigPort,
99
+ }
@@ -0,0 +1,21 @@
1
+ import { type Node } from "../network/schema";
2
+ import { type StateNode } from "../reactive/graph";
3
+ export type ConfigKind = "global" | "player";
4
+ /** One value a reader sees. Commit is its only writer. */
5
+ /** A config value as reactive state, so a read is tracked (rule 93) and a commit wakes its readers once. */
6
+ type Cell = StateNode;
7
+ export interface Declaration {
8
+ readonly name: string;
9
+ readonly kind: ConfigKind;
10
+ readonly node: Node;
11
+ readonly fallback: unknown;
12
+ /** The value admitted from the global snapshot. */
13
+ readonly global: Cell;
14
+ }
15
+ export declare function declare(name: string, kind: ConfigKind, fallback: unknown, node: Node): Declaration;
16
+ export declare function readGlobal(declaration: Declaration): unknown;
17
+ export declare function readPlayer(declaration: Declaration, player: Player): unknown;
18
+ export declare function ready(player?: Player): boolean;
19
+ export declare function refresh(): boolean;
20
+ export declare function installConfigRuntime(): void;
21
+ export {};