@trioforge/crucible 0.0.0-stage → 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 (183) hide show
  1. package/LICENSE +21 -0
  2. package/client/package.json +4 -0
  3. package/emit/package.json +4 -0
  4. package/out/all.d.ts +7 -0
  5. package/out/all.luau +2 -0
  6. package/out/attribute.d.ts +7 -0
  7. package/out/attribute.luau +7 -0
  8. package/out/client/index.d.ts +10 -0
  9. package/out/client/init.luau +55 -0
  10. package/out/configs.d.ts +37 -0
  11. package/out/configs.luau +46 -0
  12. package/out/crucible-behaviour.d.ts +16 -0
  13. package/out/crucible-behaviour.luau +78 -0
  14. package/out/crucible-object.d.ts +10 -0
  15. package/out/crucible-object.luau +69 -0
  16. package/out/data.d.ts +224 -0
  17. package/out/data.luau +97 -0
  18. package/out/emit/configs.d.ts +4 -0
  19. package/out/emit/configs.luau +22 -0
  20. package/out/emit/data.d.ts +6 -0
  21. package/out/emit/data.luau +19 -0
  22. package/out/emit/index.d.ts +45 -0
  23. package/out/emit/init.luau +101 -0
  24. package/out/emit/network.d.ts +6 -0
  25. package/out/emit/network.luau +36 -0
  26. package/out/emit/replicated.d.ts +4 -0
  27. package/out/emit/replicated.luau +13 -0
  28. package/out/index.d.ts +20 -0
  29. package/out/init.luau +34 -0
  30. package/out/inject.d.ts +1 -0
  31. package/out/inject.luau +7 -0
  32. package/out/internal/binding.d.ts +1 -0
  33. package/out/internal/binding.luau +615 -0
  34. package/out/internal/boot.d.ts +1 -0
  35. package/out/internal/boot.luau +562 -0
  36. package/out/internal/configs/limits.d.ts +10 -0
  37. package/out/internal/configs/limits.luau +25 -0
  38. package/out/internal/configs/port.d.ts +29 -0
  39. package/out/internal/configs/port.luau +99 -0
  40. package/out/internal/configs/runtime.d.ts +21 -0
  41. package/out/internal/configs/runtime.luau +981 -0
  42. package/out/internal/construction-claim.d.ts +1 -0
  43. package/out/internal/construction-claim.luau +26 -0
  44. package/out/internal/data/admission.d.ts +1 -0
  45. package/out/internal/data/admission.luau +673 -0
  46. package/out/internal/data/declarations.d.ts +1 -0
  47. package/out/internal/data/declarations.luau +384 -0
  48. package/out/internal/data/envelope.d.ts +1 -0
  49. package/out/internal/data/envelope.luau +318 -0
  50. package/out/internal/data/handles.d.ts +1 -0
  51. package/out/internal/data/handles.luau +255 -0
  52. package/out/internal/data/inbox-transforms.d.ts +1 -0
  53. package/out/internal/data/inbox-transforms.luau +458 -0
  54. package/out/internal/data/inbox.d.ts +1 -0
  55. package/out/internal/data/inbox.luau +437 -0
  56. package/out/internal/data/lease.d.ts +1 -0
  57. package/out/internal/data/lease.luau +304 -0
  58. package/out/internal/data/limits.d.ts +1 -0
  59. package/out/internal/data/limits.luau +234 -0
  60. package/out/internal/data/messages.d.ts +1 -0
  61. package/out/internal/data/messages.luau +69 -0
  62. package/out/internal/data/operation-id.d.ts +1 -0
  63. package/out/internal/data/operation-id.luau +61 -0
  64. package/out/internal/data/paths.d.ts +2 -0
  65. package/out/internal/data/paths.luau +323 -0
  66. package/out/internal/data/port.d.ts +1 -0
  67. package/out/internal/data/port.luau +290 -0
  68. package/out/internal/data/profiles.d.ts +1 -0
  69. package/out/internal/data/profiles.luau +862 -0
  70. package/out/internal/data/receipts.d.ts +1 -0
  71. package/out/internal/data/receipts.luau +190 -0
  72. package/out/internal/data/record-bindings.d.ts +1 -0
  73. package/out/internal/data/record-bindings.luau +92 -0
  74. package/out/internal/data/record-declarations.d.ts +1 -0
  75. package/out/internal/data/record-declarations.luau +140 -0
  76. package/out/internal/data/records.d.ts +1 -0
  77. package/out/internal/data/records.luau +400 -0
  78. package/out/internal/data/runtime.d.ts +1 -0
  79. package/out/internal/data/runtime.luau +918 -0
  80. package/out/internal/data/scheduler.d.ts +1 -0
  81. package/out/internal/data/scheduler.luau +507 -0
  82. package/out/internal/data/session.d.ts +1 -0
  83. package/out/internal/data/session.luau +234 -0
  84. package/out/internal/data/shutdown.d.ts +1 -0
  85. package/out/internal/data/shutdown.luau +91 -0
  86. package/out/internal/data/waiters.d.ts +1 -0
  87. package/out/internal/data/waiters.luau +65 -0
  88. package/out/internal/dense-list.d.ts +1 -0
  89. package/out/internal/dense-list.luau +155 -0
  90. package/out/internal/diagnostics.d.ts +1 -0
  91. package/out/internal/diagnostics.luau +29 -0
  92. package/out/internal/hooks.d.ts +1 -0
  93. package/out/internal/hooks.luau +53 -0
  94. package/out/internal/implementers.d.ts +1 -0
  95. package/out/internal/implementers.luau +119 -0
  96. package/out/internal/lifecycle.d.ts +1 -0
  97. package/out/internal/lifecycle.luau +912 -0
  98. package/out/internal/log-output.d.ts +1 -0
  99. package/out/internal/log-output.luau +106 -0
  100. package/out/internal/network/catalog.d.ts +26 -0
  101. package/out/internal/network/catalog.luau +182 -0
  102. package/out/internal/network/claims.d.ts +1 -0
  103. package/out/internal/network/claims.luau +154 -0
  104. package/out/internal/network/client-session.d.ts +1 -0
  105. package/out/internal/network/client-session.luau +403 -0
  106. package/out/internal/network/codec.d.ts +63 -0
  107. package/out/internal/network/codec.luau +1183 -0
  108. package/out/internal/network/envelope.d.ts +1 -0
  109. package/out/internal/network/envelope.luau +247 -0
  110. package/out/internal/network/limits.d.ts +26 -0
  111. package/out/internal/network/limits.luau +55 -0
  112. package/out/internal/network/port.d.ts +1 -0
  113. package/out/internal/network/port.luau +19 -0
  114. package/out/internal/network/roblox-network-port.d.ts +1 -0
  115. package/out/internal/network/roblox-network-port.luau +105 -0
  116. package/out/internal/network/schema.d.ts +72 -0
  117. package/out/internal/network/schema.luau +386 -0
  118. package/out/internal/network/server-session.d.ts +1 -0
  119. package/out/internal/network/server-session.luau +393 -0
  120. package/out/internal/network/session.d.ts +1 -0
  121. package/out/internal/network/session.luau +266 -0
  122. package/out/internal/port.d.ts +1 -0
  123. package/out/internal/port.luau +12 -0
  124. package/out/internal/reactive/context.d.ts +1 -0
  125. package/out/internal/reactive/context.luau +149 -0
  126. package/out/internal/reactive/flush.d.ts +1 -0
  127. package/out/internal/reactive/flush.luau +355 -0
  128. package/out/internal/reactive/graph.d.ts +1 -0
  129. package/out/internal/reactive/graph.luau +626 -0
  130. package/out/internal/reactive/limits.d.ts +1 -0
  131. package/out/internal/reactive/limits.luau +54 -0
  132. package/out/internal/reactive/ownership.d.ts +1 -0
  133. package/out/internal/reactive/ownership.luau +626 -0
  134. package/out/internal/registry.d.ts +1 -0
  135. package/out/internal/registry.luau +455 -0
  136. package/out/internal/roblox-port.d.ts +1 -0
  137. package/out/internal/roblox-port.luau +117 -0
  138. package/out/internal/runner.d.ts +1 -0
  139. package/out/internal/runner.luau +86 -0
  140. package/out/internal/scheduler.d.ts +1 -0
  141. package/out/internal/scheduler.luau +207 -0
  142. package/out/internal/state.d.ts +1 -0
  143. package/out/internal/state.luau +41 -0
  144. package/out/internal/sync/client-sync.d.ts +1 -0
  145. package/out/internal/sync/client-sync.luau +157 -0
  146. package/out/internal/sync/declarations.d.ts +1 -0
  147. package/out/internal/sync/declarations.luau +276 -0
  148. package/out/internal/sync/frame.d.ts +1 -0
  149. package/out/internal/sync/frame.luau +91 -0
  150. package/out/internal/sync/server-sync.d.ts +1 -0
  151. package/out/internal/sync/server-sync.luau +502 -0
  152. package/out/internal/sync/values.d.ts +1 -0
  153. package/out/internal/sync/values.luau +930 -0
  154. package/out/log-level.d.ts +7 -0
  155. package/out/log-level.luau +21 -0
  156. package/out/logging.d.ts +34 -0
  157. package/out/logging.luau +233 -0
  158. package/out/network.d.ts +66 -0
  159. package/out/network.luau +24 -0
  160. package/out/reactive.d.ts +76 -0
  161. package/out/reactive.luau +136 -0
  162. package/out/replicated.d.ts +38 -0
  163. package/out/replicated.luau +45 -0
  164. package/out/server/data.d.ts +54 -0
  165. package/out/server/data.luau +44 -0
  166. package/out/server/index.d.ts +18 -0
  167. package/out/server/init.luau +62 -0
  168. package/out/server/publish.d.ts +20 -0
  169. package/out/server/publish.luau +24 -0
  170. package/out/subsystem.d.ts +4 -0
  171. package/out/subsystem.luau +30 -0
  172. package/out/vide/index.d.ts +7 -0
  173. package/out/vide/init.luau +50 -0
  174. package/package.json +52 -5
  175. package/server/package.json +4 -0
  176. package/transformer/data.cjs +72 -0
  177. package/transformer/declarations.cjs +153 -0
  178. package/transformer/diagnostics.cjs +19 -0
  179. package/transformer/index.js +741 -0
  180. package/transformer/network.cjs +961 -0
  181. package/transformer/type-id.cjs +80 -0
  182. package/vide/package.json +4 -0
  183. package/README.md +0 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Trio Forge Studio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ {
2
+ "main": "../out/client/init.luau",
3
+ "types": "../out/client/index.d.ts"
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "main": "../out/emit/init.luau",
3
+ "types": "../out/emit/index.d.ts"
4
+ }
package/out/all.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export interface All<T extends object> {
2
+ size(): number;
3
+ has(item: T): boolean;
4
+ first(): T | undefined;
5
+ forEach(visit: (item: T) => void): void;
6
+ observe(added: (item: T) => void, removed?: (item: T) => void): () => void;
7
+ }
package/out/all.luau ADDED
@@ -0,0 +1,2 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ return nil
@@ -0,0 +1,7 @@
1
+ type AttributeValue = string | number | boolean | Vector3 | Color3;
2
+ export declare function attribute<T extends AttributeValue>(): T;
3
+ export declare function attribute(fallback: number): number;
4
+ export declare function attribute(fallback: string): string;
5
+ export declare function attribute(fallback: boolean): boolean;
6
+ export declare function attribute<T extends AttributeValue | undefined>(fallback: T): T;
7
+ export {};
@@ -0,0 +1,7 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local function attribute(_fallback)
3
+ error("attribute() was not replaced; add the Crucible transformer to the compiler plugins")
4
+ end
5
+ return {
6
+ attribute = attribute,
7
+ }
@@ -0,0 +1,10 @@
1
+ import type { BootOptions } from "../index";
2
+ import type { AskResult, AskServer, PayloadArgs, ToServer } from "../network";
3
+ export declare const BOOTED_ATTRIBUTE = "CrucibleBooted";
4
+ export declare function boot(options: BootOptions): void;
5
+ export declare namespace Network {
6
+ /** True when accepted locally: fired now, or queued until this client is ready. Never delivery. */
7
+ function send<P>(message: ToServer<P>, ...payload: PayloadArgs<NoInfer<P>>): boolean;
8
+ /** Yields until the reply, the declared timeout or the session ending. */
9
+ function ask<Request, Response>(message: AskServer<Request, Response>, ...request: PayloadArgs<NoInfer<Request>>): AskResult<Response>;
10
+ }
@@ -0,0 +1,55 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local bootSide = TS.import(script, script.Parent, "internal", "boot").boot
4
+ local ClientSession = TS.import(script, script.Parent, "internal", "network", "client-session").ClientSession
5
+ local networkPort = TS.import(script, script.Parent, "internal", "network", "port").networkPort
6
+ local installClientSync = TS.import(script, script.Parent, "internal", "sync", "client-sync").installClientSync
7
+ local SyncFaultMessage = TS.import(script, script.Parent, "internal", "sync", "frame").SyncFaultMessage
8
+ if not game:GetService("RunService"):IsClient() then
9
+ error("@trioforge/crucible/client loaded on the server")
10
+ end
11
+ local BOOTED_ATTRIBUTE = "CrucibleBooted"
12
+ local session
13
+ local function active()
14
+ return session or error("Crucible networking is not ready; send from a constructor, a hook, or later", 0)
15
+ end
16
+ local function boot(options)
17
+ local network = ClientSession.new(networkPort())
18
+ session = network
19
+ installClientSync(function(revision)
20
+ return active():send(SyncFaultMessage, revision)
21
+ end)
22
+ bootSide({
23
+ name = "client",
24
+ renders = true,
25
+ booted = function()
26
+ local player = game:GetService("Players").LocalPlayer
27
+ player:SetAttribute(BOOTED_ATTRIBUTE, true)
28
+ return function()
29
+ return player:SetAttribute(BOOTED_ATTRIBUTE, nil)
30
+ end
31
+ end,
32
+ network = network,
33
+ }, options)
34
+ end
35
+ local Network = {}
36
+ do
37
+ local _container = Network
38
+ --* True when accepted locally: fired now, or queued until this client is ready. Never delivery.
39
+ local function send(message, ...)
40
+ local payload = { ... }
41
+ return active():send(message, payload[1])
42
+ end
43
+ _container.send = send
44
+ --* Yields until the reply, the declared timeout or the session ending.
45
+ local function ask(message, ...)
46
+ local request = { ... }
47
+ return active():ask(message, request[1])
48
+ end
49
+ _container.ask = ask
50
+ end
51
+ return {
52
+ boot = boot,
53
+ BOOTED_ATTRIBUTE = BOOTED_ATTRIBUTE,
54
+ Network = Network,
55
+ }
@@ -0,0 +1,37 @@
1
+ declare const configKind: unique symbol;
2
+ /** An experience-wide config. Calling it reads the global snapshot's value; it never yields or throws. */
3
+ export interface Config<T> {
4
+ (): T;
5
+ readonly [configKind]: {
6
+ readonly scope: "global";
7
+ readonly value: (value: T) => T;
8
+ };
9
+ }
10
+ /** A targeted config. Calling it reads that player's value; it never yields or throws. */
11
+ export interface PlayerConfig<T> {
12
+ (player: Player): T;
13
+ readonly [configKind]: {
14
+ readonly scope: "player";
15
+ readonly value: (value: T) => T;
16
+ };
17
+ }
18
+ /**
19
+ * Declares an experience config by its dashboard key, read from the global snapshot. It must initialize an exported
20
+ * top-level const in a server module, with `T` explicit. The fallback is the value before the first fetch and for a
21
+ * missing key.
22
+ */
23
+ export declare function config<T = never>(_name: string, _fallback: NoInfer<T>): Config<T>;
24
+ /** Declares a targeted config by its dashboard key, read from each player's snapshot. Same form as `config`. */
25
+ export declare function playerConfig<T = never>(_name: string, _fallback: NoInfer<T>): PlayerConfig<T>;
26
+ /**
27
+ * With no argument, true once the global snapshot has been applied. With a player, true once that player's snapshot
28
+ * has been applied, and false before and after they leave.
29
+ */
30
+ export declare function configReady(player?: Player): boolean;
31
+ /**
32
+ * Refreshes every snapshot with newer values, fetches every snapshot that has not loaded, and applies them all in
33
+ * one pass. Yields, at most 10 seconds. True when every snapshot is current; false keeps the last good values.
34
+ * Concurrent calls share one refresh. Crucible never calls it.
35
+ */
36
+ export declare function refreshConfigs(): boolean;
37
+ export {};
@@ -0,0 +1,46 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _runtime = TS.import(script, script.Parent, "internal", "configs", "runtime")
4
+ local ready = _runtime.ready
5
+ local refresh = _runtime.refresh
6
+ --* An experience-wide config. Calling it reads the global snapshot's value; it never yields or throws.
7
+ --* A targeted config. Calling it reads that player's value; it never yields or throws.
8
+ --[[
9
+ *
10
+ * Declares an experience config by its dashboard key, read from the global snapshot. It must initialize an exported
11
+ * top-level const in a server module, with `T` explicit. The fallback is the value before the first fetch and for a
12
+ * missing key.
13
+
14
+ ]]
15
+ local function config(_name, _fallback)
16
+ error("Crucible configs need the @trioforge/crucible/transformer plugin")
17
+ end
18
+ --* Declares a targeted config by its dashboard key, read from each player's snapshot. Same form as `config`.
19
+ local function playerConfig(_name, _fallback)
20
+ error("Crucible configs need the @trioforge/crucible/transformer plugin")
21
+ end
22
+ --[[
23
+ *
24
+ * With no argument, true once the global snapshot has been applied. With a player, true once that player's snapshot
25
+ * has been applied, and false before and after they leave.
26
+
27
+ ]]
28
+ local function configReady(player)
29
+ return ready(player)
30
+ end
31
+ --[[
32
+ *
33
+ * Refreshes every snapshot with newer values, fetches every snapshot that has not loaded, and applies them all in
34
+ * one pass. Yields, at most 10 seconds. True when every snapshot is current; false keeps the last good values.
35
+ * Concurrent calls share one refresh. Crucible never calls it.
36
+
37
+ ]]
38
+ local function refreshConfigs()
39
+ return refresh()
40
+ end
41
+ return {
42
+ config = config,
43
+ playerConfig = playerConfig,
44
+ configReady = configReady,
45
+ refreshConfigs = refreshConfigs,
46
+ }
@@ -0,0 +1,16 @@
1
+ import { CrucibleObject } from "./crucible-object";
2
+ export type BehaviourClass<T extends CrucibleBehaviour<string>> = new (...args: never[]) => T;
3
+ export declare abstract class CrucibleBehaviour<Tag extends string, I extends Instance = Instance> extends CrucibleObject {
4
+ static accepts(instance: Instance): boolean;
5
+ static refresh(instance: Instance): void;
6
+ static get<T extends CrucibleBehaviour<string>>(this: BehaviourClass<T>, instance: Instance): T | undefined;
7
+ readonly instance: I;
8
+ readonly tag: Tag;
9
+ constructor();
10
+ isEnabled(): boolean;
11
+ setEnabled(enabled: boolean): void;
12
+ remove(): void;
13
+ protected onEnable(): void;
14
+ protected onDisable(): void;
15
+ protected onDestroy(): void;
16
+ }
@@ -0,0 +1,78 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local CrucibleObject = TS.import(script, script.Parent, "crucible-object").CrucibleObject
4
+ local _binding = TS.import(script, script.Parent, "internal", "binding")
5
+ local boundTo = _binding.boundTo
6
+ local defaultAccepts = _binding.defaultAccepts
7
+ local refresh = _binding.refresh
8
+ local remove = _binding.remove
9
+ local readClaim = TS.import(script, script.Parent, "internal", "construction-claim").readClaim
10
+ local installBehaviourHooks = TS.import(script, script.Parent, "internal", "hooks").installBehaviourHooks
11
+ local _lifecycle = TS.import(script, script.Parent, "internal", "lifecycle")
12
+ local isLive = _lifecycle.isLive
13
+ local request = _lifecycle.request
14
+ local stateOf = _lifecycle.stateOf
15
+ local CrucibleBehaviour
16
+ do
17
+ local super = CrucibleObject
18
+ CrucibleBehaviour = setmetatable({}, {
19
+ __tostring = function()
20
+ return "CrucibleBehaviour"
21
+ end,
22
+ __index = super,
23
+ })
24
+ CrucibleBehaviour.__index = CrucibleBehaviour
25
+ function CrucibleBehaviour:constructor()
26
+ super.constructor(self)
27
+ local state = readClaim() or error("CrucibleBehaviour constructed outside Crucible")
28
+ self.instance = state.instance
29
+ end
30
+ function CrucibleBehaviour:accepts(instance)
31
+ return defaultAccepts(instance)
32
+ end
33
+ function CrucibleBehaviour:refresh(instance)
34
+ refresh(instance)
35
+ end
36
+ function CrucibleBehaviour:get(instance)
37
+ local state = boundTo(self, instance)
38
+ return if state ~= nil and isLive(state) then (state.object) else nil
39
+ end
40
+ function CrucibleBehaviour:isEnabled()
41
+ local state = stateOf(self)
42
+ return state ~= nil and state.phase == 3 and isLive(state)
43
+ end
44
+ function CrucibleBehaviour:setEnabled(enabled)
45
+ local state = stateOf(self)
46
+ if state ~= nil then
47
+ request(state, if enabled then 0 else 1)
48
+ end
49
+ end
50
+ function CrucibleBehaviour:remove()
51
+ local state = stateOf(self)
52
+ if state ~= nil then
53
+ remove(state)
54
+ end
55
+ end
56
+ function CrucibleBehaviour:onEnable()
57
+ end
58
+ function CrucibleBehaviour:onDisable()
59
+ end
60
+ function CrucibleBehaviour:onDestroy()
61
+ end
62
+ do
63
+ installBehaviourHooks({
64
+ onEnable = function(b)
65
+ return b:onEnable()
66
+ end,
67
+ onDisable = function(b)
68
+ return b:onDisable()
69
+ end,
70
+ onDestroy = function(b)
71
+ return b:onDestroy()
72
+ end,
73
+ })
74
+ end
75
+ end
76
+ return {
77
+ CrucibleBehaviour = CrucibleBehaviour,
78
+ }
@@ -0,0 +1,10 @@
1
+ export declare abstract class CrucibleObject {
2
+ static readonly renderPriority?: number;
3
+ constructor();
4
+ protected awake(): void;
5
+ protected start(): void;
6
+ protected fixedUpdate(_dt: number): void;
7
+ protected update(_dt: number): void;
8
+ protected lateUpdate(_dt: number): void;
9
+ protected preRender(_dt: number): void;
10
+ }
@@ -0,0 +1,69 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local readClaim = TS.import(script, script.Parent, "internal", "construction-claim").readClaim
4
+ local _hooks = TS.import(script, script.Parent, "internal", "hooks")
5
+ local FRAME_HOOK_NAMES = _hooks.FRAME_HOOK_NAMES
6
+ local installObjectHooks = _hooks.installObjectHooks
7
+ local CrucibleObject
8
+ do
9
+ CrucibleObject = {}
10
+ function CrucibleObject:constructor()
11
+ local state = readClaim() or error("CrucibleObject constructed outside Crucible")
12
+ state.object = self
13
+ end
14
+ function CrucibleObject:awake()
15
+ end
16
+ function CrucibleObject:start()
17
+ end
18
+ function CrucibleObject:fixedUpdate(_dt)
19
+ end
20
+ function CrucibleObject:update(_dt)
21
+ end
22
+ function CrucibleObject:lateUpdate(_dt)
23
+ end
24
+ function CrucibleObject:preRender(_dt)
25
+ end
26
+ do
27
+ installObjectHooks({
28
+ awake = function(o)
29
+ return o:awake()
30
+ end,
31
+ start = function(o)
32
+ return o:start()
33
+ end,
34
+ frame = {
35
+ [0] = function(o, dt)
36
+ return o:preRender(dt)
37
+ end,
38
+ [1] = function(o, dt)
39
+ return o:fixedUpdate(dt)
40
+ end,
41
+ [2] = function(o, dt)
42
+ return o:update(dt)
43
+ end,
44
+ [3] = function(o, dt)
45
+ return o:lateUpdate(dt)
46
+ end,
47
+ },
48
+ overrides = function(klass)
49
+ local methods = klass
50
+ local defaults = CrucibleObject
51
+ local mask = 0
52
+ -- ▼ ReadonlyArray.forEach ▼
53
+ local _callback = function(name, hook)
54
+ if methods[name] ~= defaults[name] then
55
+ mask = bit32.bor(mask, bit32.lshift(1, hook))
56
+ end
57
+ end
58
+ for _k, _v in FRAME_HOOK_NAMES do
59
+ _callback(_v, _k - 1, FRAME_HOOK_NAMES)
60
+ end
61
+ -- ▲ ReadonlyArray.forEach ▲
62
+ return mask
63
+ end,
64
+ })
65
+ end
66
+ end
67
+ return {
68
+ CrucibleObject = CrucibleObject,
69
+ }
package/out/data.d.ts ADDED
@@ -0,0 +1,224 @@
1
+ import type { Readable } from "./reactive";
2
+ declare const operationKind: unique symbol;
3
+ declare const storeKind: unique symbol;
4
+ declare const messageKind: unique symbol;
5
+ declare const claimKind: unique symbol;
6
+ declare const serverOnlyKind: unique symbol;
7
+ /** A value a save can hold: JSON, with absent keys for values that admit `undefined`. */
8
+ export type Json = boolean | number | string | readonly Json[] | JsonRecord;
9
+ /** A saved record: string keys to JSON. */
10
+ export interface JsonRecord {
11
+ readonly [key: string]: Json | undefined;
12
+ }
13
+ /**
14
+ * One schema step. It gets a private copy of the stored record, unknown keys included, as plain JSON, and returns a
15
+ * patch: an object merges into the stored object, any other value replaces the stored one, and an omitted key is kept.
16
+ * A step cannot delete a key. Steps are append-only and must be pure.
17
+ */
18
+ export type Migration = (old: JsonRecord) => JsonRecord;
19
+ /**
20
+ * The identity of one logical operation, kept across its retries. Only `operationId()` makes one; a plain string does
21
+ * not type-check. It carries its creation time, which bounds how long it deduplicates.
22
+ */
23
+ export type OperationId = string & {
24
+ readonly [operationKind]: true;
25
+ };
26
+ /** Why a store call did not complete. */
27
+ export type StoreFailure = "unavailable" | "timeout" | "uncertain" | "overloaded" | "full" | "expired" | "reused" | "closing";
28
+ /** A store call's result: narrow `ok` before reading `value`. */
29
+ export type StoreResult<T> = {
30
+ readonly ok: true;
31
+ readonly value: T;
32
+ } | {
33
+ readonly ok: false;
34
+ readonly reason: StoreFailure;
35
+ };
36
+ /**
37
+ * A template field the owner's client never receives. The brand is phantom; `serverOnly(value)` also marks the value at
38
+ * runtime, and the declaration checks that the two agree.
39
+ */
40
+ export interface ServerOnly<V> {
41
+ readonly [serverOnlyKind]: V;
42
+ }
43
+ type Scalar = string | number | boolean;
44
+ /** What the server reads and writes: the template's type with every `ServerOnly` unwrapped, deeply readonly. */
45
+ export type ProfileData<T> = T extends ServerOnly<infer V> ? ProfileData<V> : T extends Scalar | undefined ? T : T extends ReadonlyMap<infer K, infer V> ? ReadonlyMap<K, ProfileData<V>> : {
46
+ readonly [K in keyof T]: ProfileData<T[K]>;
47
+ };
48
+ type IsUnion<V, C = V> = V extends unknown ? ([C] extends [V] ? false : true) : never;
49
+ /** A `ServerOnly` field, or a plain object field whose every field is server-only in turn. */
50
+ type ServerOnlyField<V> = [V] extends [ServerOnly<unknown>] ? true : [V] extends [Scalar | undefined] ? false : [V] extends [ReadonlyMap<unknown, unknown> | readonly unknown[]] ? false : true extends IsUnion<V> ? false : string extends keyof V ? false : [keyof V] extends [never] ? false : false extends {
51
+ [K in keyof V]-?: ServerOnlyField<NonNullable<V[K]>>;
52
+ }[keyof V] ? false : true;
53
+ /**
54
+ * What the owner's client reads: every `ServerOnly` field removed, and every plain object left with no field, at any
55
+ * depth. Deeply readonly.
56
+ */
57
+ export type ClientData<T> = T extends Scalar | undefined ? T : T extends ReadonlyMap<infer K, infer V> ? ReadonlyMap<K, ClientData<V>> : T extends readonly unknown[] ? {
58
+ readonly [K in keyof T]: ClientData<T[K]>;
59
+ } : {
60
+ readonly [K in keyof T as ServerOnlyField<NonNullable<T[K]>> extends true ? never : K]: ClientData<T[K]>;
61
+ };
62
+ /** A store's server data type, for helper signatures: `export type PlayerData = Profile<typeof PlayerData>`. */
63
+ export type Profile<S> = S extends ProfileStore<infer T> ? ProfileData<T> : never;
64
+ /** A store's client data type. */
65
+ export type ClientProfile<S> = S extends ProfileStore<infer T> ? ClientData<T> : never;
66
+ /**
67
+ * A server handle's own members. `h()` reads, tracked and frozen; while the profile is not writable it returns the
68
+ * template's value at the path. `set` and `update` are one write each through the store's gate and return its result.
69
+ */
70
+ export interface WriteHandle<V, A> extends Readable<V | A> {
71
+ set(value: V): boolean;
72
+ update(change: (current: V) => V): boolean;
73
+ }
74
+ /** An array handle's writes. Indices are TypeScript's, from 0. */
75
+ export interface ArrayWrites<E> {
76
+ /** Inserts at `index`, from 0 to the length, or appends without one. */
77
+ insert(value: E, index?: number): boolean;
78
+ /** Removes the element at `index`, from 0 to length - 1. */
79
+ remove(index: number): boolean;
80
+ }
81
+ type DataChildren<V, A> = [V] extends [Scalar] ? unknown : [V] extends [readonly (infer E)[]] ? ArrayWrites<E> & {
82
+ readonly [index: number]: DataHandle<E, undefined>;
83
+ } : true extends IsUnion<V> ? unknown : [V] extends [ReadonlyMap<string, infer E>] ? {
84
+ readonly [key: string]: DataHandle<E | undefined, A>;
85
+ } : string extends keyof V ? {
86
+ readonly [key: string]: DataHandle<V[string & keyof V] | undefined, A>;
87
+ } : {
88
+ readonly [K in keyof V]-?: DataHandle<V[K], A>;
89
+ };
90
+ /** A server handle at a position of type `V`: reads `V | A`, writes `V`, and reaches its children by key. */
91
+ export type DataHandle<V, A = never> = WriteHandle<V, A> & DataChildren<NonNullable<V>, A | Extract<V, undefined>>;
92
+ type ReadChildren<V, A> = [V] extends [Scalar] ? unknown : [V] extends [readonly (infer E)[]] ? {
93
+ readonly [index: number]: ReadHandle<E, undefined>;
94
+ } : true extends IsUnion<V> ? unknown : [V] extends [ReadonlyMap<string, infer E>] ? {
95
+ readonly [key: string]: ReadHandle<E | undefined, A>;
96
+ } : string extends keyof V ? {
97
+ readonly [key: string]: ReadHandle<V[string & keyof V] | undefined, A>;
98
+ } : {
99
+ readonly [K in keyof V]-?: ReadHandle<V[K], A>;
100
+ };
101
+ /** A client handle: the same paths and reads as the server's, and no writes. */
102
+ export type ReadHandle<V, A = never> = Readable<V | A> & ReadChildren<NonNullable<V>, A | Extract<V, undefined>>;
103
+ /** Why a profile loaded. */
104
+ export type LoadReason = "join" | "resume";
105
+ /** Why a profile is releasing. */
106
+ export type ReleaseReason = "leave" | "handoff" | "shutdown";
107
+ /** A developer product purchase, read from Roblox's receipt. */
108
+ export interface Purchase {
109
+ readonly id: string;
110
+ readonly productId: number;
111
+ readonly currencySpent: number;
112
+ }
113
+ /**
114
+ * A grant callback's fourth argument. It stages an effect that runs once, on a fresh thread, after the grant is durably
115
+ * saved and before the purchase is acknowledged; never after a refused grant.
116
+ */
117
+ export type AfterGrant = (effect: () => void) => void;
118
+ /** A field holding it is the claim. Not callable. */
119
+ export interface ProfileClaim {
120
+ readonly [claimKind]: true;
121
+ }
122
+ /** A durable message to one player's profile, declared in a server module. Invariant in `P`. */
123
+ export interface ProfileMessage<P> {
124
+ readonly [messageKind]: {
125
+ readonly payload: (value: P) => P;
126
+ };
127
+ }
128
+ /** One quarantined message, for recovery tooling. The payload is the stored JSON, unknown keys included. */
129
+ export interface QuarantinedMessage {
130
+ readonly id: OperationId;
131
+ readonly name: string;
132
+ readonly reason: "payload" | "attempts";
133
+ readonly attempts: number;
134
+ /** The id's creation time, in wall-clock seconds. */
135
+ readonly sentAt: number;
136
+ readonly payload: Json;
137
+ }
138
+ /** Why a save or handoff did not confirm. */
139
+ export type SaveFailure = "unavailable" | "timeout" | "uncertain";
140
+ /** A save or handoff result: narrow `ok` first. */
141
+ export type SaveResult = {
142
+ readonly ok: true;
143
+ } | {
144
+ readonly ok: false;
145
+ readonly reason: SaveFailure;
146
+ };
147
+ /**
148
+ * A profile store: one shared declaration, callable on both sides. The server calls it with a player for that player's
149
+ * writable handle; the client calls it with nothing for the local player's read-only handle.
150
+ */
151
+ export interface ProfileStore<T> {
152
+ readonly [storeKind]: {
153
+ readonly kind: "profiles";
154
+ readonly value: (value: T) => T;
155
+ };
156
+ /** Server: the player's root handle, the same one while they are present. */
157
+ (player: Player): DataHandle<ProfileData<T>>;
158
+ /** Client: the local player's read-only root handle. */
159
+ (): ReadHandle<ClientData<T>>;
160
+ /** Server. Tracked: whether a write to the player's profile would be accepted now. */
161
+ loaded(player: Player): boolean;
162
+ /** Client. Tracked: whether the server has published this player's profile and the client holds all of it. */
163
+ loaded(): boolean;
164
+ /** Server. Tracked: the players whose profiles are writable; it changes only when one starts or stops being. */
165
+ players(): readonly Player[];
166
+ /** Server. Yields until the profile is writable (true), or its load fails, the player leaves or the server closes. */
167
+ await(player: Player): boolean;
168
+ /** Server. Yields until the current revision is durably saved, for a rare critical point. */
169
+ save(player: Player, timeoutSeconds?: number): SaveResult;
170
+ /** Server. Seals and durably releases the profile while the player stays, before a teleport. */
171
+ handoff(player: Player): SaveResult;
172
+ /** Server. After a failed teleport: waits for the handoff's release to settle, then loads again. */
173
+ resume(player: Player): boolean;
174
+ /** Server. Reads any user's saved data, with no lock and no write. */
175
+ inspect(userId: number): StoreResult<ProfileData<T> | undefined>;
176
+ /** Server. Appends a message to a user's inbox, online or not, and waits until it is durable. */
177
+ send<P>(userId: number, message: ProfileMessage<P>, payload: NoInfer<P>, id?: OperationId): StoreResult<undefined>;
178
+ /** Server. A user's quarantined messages, read with no lock. */
179
+ quarantined(userId: number): StoreResult<readonly QuarantinedMessage[]>;
180
+ /** Server. Moves listed quarantined messages back to the inbox ("retry") or drops them for good ("discard"). */
181
+ clearQuarantined(userId: number, ids: readonly OperationId[], action: "retry" | "discard"): StoreResult<number>;
182
+ /** Server claim, a Subsystem field initializer: changes a profile as it loads, before anything reads it. */
183
+ onLoad(transform: (player: Player, data: ProfileData<T>, reason: LoadReason) => ProfileData<T>): ProfileClaim;
184
+ /** Server claim: changes a profile as it releases. A claim that throws is skipped. */
185
+ onRelease(transform: (player: Player, data: ProfileData<T>, reason: ReleaseReason) => ProfileData<T>): ProfileClaim;
186
+ /** Server claim: applies a message to its target's profile, once per message id. */
187
+ receive<P>(message: ProfileMessage<P>, apply: (player: Player, data: ProfileData<T>, payload: P) => ProfileData<T>): ProfileClaim;
188
+ /** Server claim: grants a developer product. The purchase is acknowledged only after the change is saved. */
189
+ grant(productId: number, apply: (player: Player, data: ProfileData<T>, purchase: Purchase, afterGrant: AfterGrant) => ProfileData<T>): ProfileClaim;
190
+ }
191
+ /** A profile store's options. */
192
+ export interface ProfileOptions<T> {
193
+ /**
194
+ * The store's type, by inference, and a new profile's value. A missing required key on an object path from the root
195
+ * takes its value from here. Admitted and frozen once, with every `serverOnly` mark unwrapped.
196
+ */
197
+ readonly template: T;
198
+ /** Append-only. Removing or reordering a step changes what every older record becomes. */
199
+ readonly migrations?: readonly Migration[];
200
+ }
201
+ /** The handle member names. A template key with one of these names, at any depth, does not compile. */
202
+ export type ReservedName = "set" | "update" | "insert" | "remove";
203
+ type ReservedKeys<T> = T extends ServerOnly<infer V> ? ReservedKeys<V> : T extends Scalar | undefined ? never : T extends ReadonlyMap<unknown, infer V> ? ReservedKeys<V> : T extends readonly (infer E)[] ? ReservedKeys<E> : string extends keyof T ? ReservedKeys<T[string & keyof T]> : (keyof T & ReservedName) | {
204
+ [K in keyof T]-?: ReservedKeys<T[K]>;
205
+ }[keyof T];
206
+ type ReservedKeyCheck<T> = [ReservedKeys<T>] extends [never] ? unknown : {
207
+ readonly template: {
208
+ readonly [K in ReservedKeys<T> as `"${K & string}" is a handle method; rename it`]: never;
209
+ };
210
+ };
211
+ /**
212
+ * Declares a profile store by its DataStore name. It must initialize an exported top-level const in a shared module.
213
+ * The type comes from `template`; annotate empty collections and optional fields with `as`.
214
+ */
215
+ export declare function profileStore<T extends object>(_name: string, _options: ProfileOptions<T> & ReservedKeyCheck<T>): ProfileStore<T>;
216
+ /** Marks a template field server-only: the owner's client never receives it. */
217
+ export declare function serverOnly<V>(value: V): ServerOnly<V>;
218
+ /**
219
+ * The exact field paths the owner's client receives, each an array of segments: an object field's key, "[]" for an
220
+ * array element and "*" for a map value. A key that is "[]" or "*", or starts with a backslash, gets one more backslash.
221
+ * Sorted segment by segment, by bytes.
222
+ */
223
+ export declare function replicatedPaths<T>(store: ProfileStore<T>): readonly (readonly string[])[];
224
+ export {};