@trioforge/crucible 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/client/index.d.ts +7 -0
- package/out/client/init.luau +36 -1
- package/out/configs.d.ts +37 -0
- package/out/configs.luau +46 -0
- package/out/data.d.ts +224 -0
- package/out/data.luau +97 -0
- package/out/emit/configs.d.ts +4 -0
- package/out/emit/configs.luau +22 -0
- package/out/emit/data.d.ts +6 -0
- package/out/emit/data.luau +19 -0
- package/out/emit/index.d.ts +5 -1
- package/out/emit/init.luau +23 -7
- package/out/emit/network.d.ts +6 -0
- package/out/emit/network.luau +36 -0
- package/out/emit/replicated.d.ts +4 -0
- package/out/emit/replicated.luau +13 -0
- package/out/index.d.ts +8 -0
- package/out/init.luau +23 -0
- package/out/internal/binding.luau +5 -0
- package/out/internal/boot.luau +107 -8
- package/out/internal/configs/limits.d.ts +10 -0
- package/out/internal/configs/limits.luau +25 -0
- package/out/internal/configs/port.d.ts +29 -0
- package/out/internal/configs/port.luau +99 -0
- package/out/internal/configs/runtime.d.ts +21 -0
- package/out/internal/configs/runtime.luau +981 -0
- package/out/internal/data/admission.d.ts +1 -0
- package/out/internal/data/admission.luau +673 -0
- package/out/internal/data/declarations.d.ts +1 -0
- package/out/internal/data/declarations.luau +384 -0
- package/out/internal/data/envelope.d.ts +1 -0
- package/out/internal/data/envelope.luau +318 -0
- package/out/internal/data/handles.d.ts +1 -0
- package/out/internal/data/handles.luau +255 -0
- package/out/internal/data/inbox-transforms.d.ts +1 -0
- package/out/internal/data/inbox-transforms.luau +458 -0
- package/out/internal/data/inbox.d.ts +1 -0
- package/out/internal/data/inbox.luau +437 -0
- package/out/internal/data/lease.d.ts +1 -0
- package/out/internal/data/lease.luau +304 -0
- package/out/internal/data/limits.d.ts +1 -0
- package/out/internal/data/limits.luau +234 -0
- package/out/internal/data/messages.d.ts +1 -0
- package/out/internal/data/messages.luau +69 -0
- package/out/internal/data/operation-id.d.ts +1 -0
- package/out/internal/data/operation-id.luau +61 -0
- package/out/internal/data/paths.d.ts +2 -0
- package/out/internal/data/paths.luau +323 -0
- package/out/internal/data/port.d.ts +1 -0
- package/out/internal/data/port.luau +290 -0
- package/out/internal/data/profiles.d.ts +1 -0
- package/out/internal/data/profiles.luau +862 -0
- package/out/internal/data/receipts.d.ts +1 -0
- package/out/internal/data/receipts.luau +190 -0
- package/out/internal/data/record-bindings.d.ts +1 -0
- package/out/internal/data/record-bindings.luau +92 -0
- package/out/internal/data/record-declarations.d.ts +1 -0
- package/out/internal/data/record-declarations.luau +140 -0
- package/out/internal/data/records.d.ts +1 -0
- package/out/internal/data/records.luau +400 -0
- package/out/internal/data/runtime.d.ts +1 -0
- package/out/internal/data/runtime.luau +918 -0
- package/out/internal/data/scheduler.d.ts +1 -0
- package/out/internal/data/scheduler.luau +507 -0
- package/out/internal/data/session.d.ts +1 -0
- package/out/internal/data/session.luau +234 -0
- package/out/internal/data/shutdown.d.ts +1 -0
- package/out/internal/data/shutdown.luau +91 -0
- package/out/internal/data/waiters.d.ts +1 -0
- package/out/internal/data/waiters.luau +65 -0
- package/out/internal/lifecycle.luau +45 -0
- package/out/internal/network/catalog.d.ts +26 -0
- package/out/internal/network/catalog.luau +182 -0
- package/out/internal/network/claims.d.ts +1 -0
- package/out/internal/network/claims.luau +154 -0
- package/out/internal/network/client-session.d.ts +1 -0
- package/out/internal/network/client-session.luau +403 -0
- package/out/internal/network/codec.d.ts +63 -0
- package/out/internal/network/codec.luau +1183 -0
- package/out/internal/network/envelope.d.ts +1 -0
- package/out/internal/network/envelope.luau +247 -0
- package/out/internal/network/limits.d.ts +26 -0
- package/out/internal/network/limits.luau +55 -0
- package/out/internal/network/port.d.ts +1 -0
- package/out/internal/network/port.luau +19 -0
- package/out/internal/network/roblox-network-port.d.ts +1 -0
- package/out/internal/network/roblox-network-port.luau +105 -0
- package/out/internal/network/schema.d.ts +72 -0
- package/out/internal/network/schema.luau +386 -0
- package/out/internal/network/server-session.d.ts +1 -0
- package/out/internal/network/server-session.luau +393 -0
- package/out/internal/network/session.d.ts +1 -0
- package/out/internal/network/session.luau +266 -0
- package/out/internal/reactive/context.d.ts +1 -0
- package/out/internal/reactive/context.luau +149 -0
- package/out/internal/reactive/flush.d.ts +1 -0
- package/out/internal/reactive/flush.luau +355 -0
- package/out/internal/reactive/graph.d.ts +1 -0
- package/out/internal/reactive/graph.luau +626 -0
- package/out/internal/reactive/limits.d.ts +1 -0
- package/out/internal/reactive/limits.luau +54 -0
- package/out/internal/reactive/ownership.d.ts +1 -0
- package/out/internal/reactive/ownership.luau +626 -0
- package/out/internal/registry.luau +10 -1
- package/out/internal/sync/client-sync.d.ts +1 -0
- package/out/internal/sync/client-sync.luau +157 -0
- package/out/internal/sync/declarations.d.ts +1 -0
- package/out/internal/sync/declarations.luau +276 -0
- package/out/internal/sync/frame.d.ts +1 -0
- package/out/internal/sync/frame.luau +91 -0
- package/out/internal/sync/server-sync.d.ts +1 -0
- package/out/internal/sync/server-sync.luau +502 -0
- package/out/internal/sync/values.d.ts +1 -0
- package/out/internal/sync/values.luau +930 -0
- package/out/network.d.ts +66 -0
- package/out/network.luau +24 -0
- package/out/reactive.d.ts +76 -0
- package/out/reactive.luau +136 -0
- package/out/replicated.d.ts +38 -0
- package/out/replicated.luau +45 -0
- package/out/server/data.d.ts +54 -0
- package/out/server/data.luau +44 -0
- package/out/server/index.d.ts +16 -0
- package/out/server/init.luau +49 -3
- package/out/server/publish.d.ts +20 -0
- package/out/server/publish.luau +24 -0
- package/out/vide/index.d.ts +7 -0
- package/out/vide/init.luau +50 -0
- package/package.json +13 -2
- package/transformer/data.cjs +72 -0
- package/transformer/declarations.cjs +153 -0
- package/transformer/index.js +37 -11
- package/transformer/network.cjs +961 -0
- package/vide/package.json +4 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local _vide = TS.import(script, TS.getModule(script, "@rbxts", "vide").src)
|
|
4
|
+
local cleanup = _vide.cleanup
|
|
5
|
+
local source = _vide.source
|
|
6
|
+
local untracked = TS.import(script, script.Parent, "reactive").untracked
|
|
7
|
+
local createWatchFrom = TS.import(script, script.Parent, "internal", "reactive", "flush").createWatchFrom
|
|
8
|
+
local createRootScope = TS.import(script, script.Parent, "internal", "reactive", "ownership").createRootScope
|
|
9
|
+
local NO_OWNER = "useReadable() needs a Vide owner: call it inside a component or root()"
|
|
10
|
+
--[[
|
|
11
|
+
*
|
|
12
|
+
* A Vide source that follows `readable`. It holds the value read now, untracked, and takes each change on the
|
|
13
|
+
* reactive flush, through a `watch` that ends with the current Vide owner. Outside a Vide component or `root` it
|
|
14
|
+
* throws.
|
|
15
|
+
|
|
16
|
+
]]
|
|
17
|
+
local function useReadable(readable)
|
|
18
|
+
local dispose
|
|
19
|
+
-- Vide's cleanup is the only public test for an owner, and registering first leaves nothing to undo.
|
|
20
|
+
local owned = pcall(cleanup, function()
|
|
21
|
+
local _result = dispose
|
|
22
|
+
if _result ~= nil then
|
|
23
|
+
_result = _result()
|
|
24
|
+
end
|
|
25
|
+
return _result
|
|
26
|
+
end)
|
|
27
|
+
if not owned then
|
|
28
|
+
error(NO_OWNER, 2)
|
|
29
|
+
end
|
|
30
|
+
local initial = untracked(readable)
|
|
31
|
+
local value = source(initial)
|
|
32
|
+
local latest = initial
|
|
33
|
+
local write = function()
|
|
34
|
+
value(latest)
|
|
35
|
+
return nil
|
|
36
|
+
end
|
|
37
|
+
-- A root, so a component mounted from inside a Crucible run or callback does not end with it.
|
|
38
|
+
dispose = createRootScope(function()
|
|
39
|
+
return createWatchFrom(readable, initial, function(current)
|
|
40
|
+
latest = current
|
|
41
|
+
-- Vide runs its effects inside the write. Across gsub's C call a yield in one throws where it yields, so
|
|
42
|
+
-- Vide's own error path pops its scope stack. Without it the runner cancels the yielded worker mid-effect.
|
|
43
|
+
string.gsub("x", "x", write)
|
|
44
|
+
end)
|
|
45
|
+
end)
|
|
46
|
+
return value
|
|
47
|
+
end
|
|
48
|
+
return {
|
|
49
|
+
useReadable = useReadable,
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trioforge/crucible",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "out/init.luau",
|
|
6
6
|
"types": "out/index.d.ts",
|
|
@@ -21,10 +21,20 @@
|
|
|
21
21
|
"types": "./out/client/index.d.ts",
|
|
22
22
|
"default": "./out/client/init.luau"
|
|
23
23
|
},
|
|
24
|
+
"./vide": {
|
|
25
|
+
"types": "./out/vide/index.d.ts",
|
|
26
|
+
"default": "./out/vide/init.luau"
|
|
27
|
+
},
|
|
24
28
|
"./transformer": "./transformer/index.js"
|
|
25
29
|
},
|
|
26
30
|
"peerDependencies": {
|
|
27
|
-
"typescript": "5.5.3"
|
|
31
|
+
"typescript": "5.5.3",
|
|
32
|
+
"@rbxts/vide": "^0.6.1"
|
|
33
|
+
},
|
|
34
|
+
"peerDependenciesMeta": {
|
|
35
|
+
"@rbxts/vide": {
|
|
36
|
+
"optional": true
|
|
37
|
+
}
|
|
28
38
|
},
|
|
29
39
|
"files": [
|
|
30
40
|
"out",
|
|
@@ -32,6 +42,7 @@
|
|
|
32
42
|
"server",
|
|
33
43
|
"client",
|
|
34
44
|
"emit",
|
|
45
|
+
"vide",
|
|
35
46
|
"LICENSE"
|
|
36
47
|
],
|
|
37
48
|
"repository": {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
function createDataChecks({ ts, program, checker, api }) {
|
|
2
|
+
const unwrap = (expression) => {
|
|
3
|
+
while (ts.isParenthesizedExpression(expression) || ts.isNonNullExpression(expression))
|
|
4
|
+
expression = expression.expression;
|
|
5
|
+
return expression;
|
|
6
|
+
};
|
|
7
|
+
const member = (expression) => {
|
|
8
|
+
if (ts.isPropertyAccessExpression(expression)) return expression.name.text;
|
|
9
|
+
if (ts.isElementAccessExpression(expression) && ts.isStringLiteralLike(expression.argumentExpression))
|
|
10
|
+
return expression.argumentExpression.text;
|
|
11
|
+
};
|
|
12
|
+
const store = (expression) => api(checker.getTypeAtLocation(expression).getSymbol()) === "ProfileStore";
|
|
13
|
+
const marketplace = (expression) => {
|
|
14
|
+
const symbol = checker.getTypeAtLocation(expression).getSymbol();
|
|
15
|
+
return (
|
|
16
|
+
symbol?.name === "MarketplaceService" &&
|
|
17
|
+
symbol.declarations?.some((d) =>
|
|
18
|
+
d.getSourceFile().fileName.replaceAll("\\", "/").includes("/@rbxts/types/"),
|
|
19
|
+
)
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
const storeCall = (node) => {
|
|
23
|
+
const callee = unwrap(node.expression);
|
|
24
|
+
if (store(callee)) return { store: callee };
|
|
25
|
+
const name = member(callee);
|
|
26
|
+
if (name !== undefined && store(callee.expression)) return { store: callee.expression, member: name };
|
|
27
|
+
};
|
|
28
|
+
let granted;
|
|
29
|
+
const grants = () => {
|
|
30
|
+
if (granted === undefined) {
|
|
31
|
+
granted = false;
|
|
32
|
+
const walk = (node) => {
|
|
33
|
+
if (granted) return;
|
|
34
|
+
if (ts.isCallExpression(node) && storeCall(node)?.member === "grant") granted = true;
|
|
35
|
+
else ts.forEachChild(node, walk);
|
|
36
|
+
};
|
|
37
|
+
for (const source of program.getSourceFiles()) if (!source.isDeclarationFile) walk(source);
|
|
38
|
+
}
|
|
39
|
+
return granted;
|
|
40
|
+
};
|
|
41
|
+
return ({ report, side }) =>
|
|
42
|
+
(node) => {
|
|
43
|
+
if (ts.isCallExpression(node)) {
|
|
44
|
+
const call = storeCall(node);
|
|
45
|
+
if (call) {
|
|
46
|
+
const text = call.store.getText();
|
|
47
|
+
const client =
|
|
48
|
+
call.member === undefined || call.member === "loaded" ? !node.arguments.length : false;
|
|
49
|
+
const required = client ? "client" : "server";
|
|
50
|
+
if (side(node) !== required)
|
|
51
|
+
report(
|
|
52
|
+
node,
|
|
53
|
+
"CRU027",
|
|
54
|
+
client
|
|
55
|
+
? `${text}${call.member ? ".loaded" : ""}() reads the local player's profile; on the server call ${text}${call.member ? ".loaded" : ""}(player).`
|
|
56
|
+
: `${text}${call.member === undefined ? "(player)" : `.${call.member}`} is server-only; the client reads ${text}().`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
61
|
+
const target = unwrap(node.left);
|
|
62
|
+
if (member(target) === "ProcessReceipt" && marketplace(target.expression) && grants())
|
|
63
|
+
report(
|
|
64
|
+
node,
|
|
65
|
+
"CRU025",
|
|
66
|
+
"Crucible's data module owns ProcessReceipt; register grants with a profile store's grant(productId, ...).",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { createDataChecks };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
const limits = Object.freeze({
|
|
2
|
+
MESSAGE_NAME_MAX_BYTES: 64,
|
|
3
|
+
MAX_ASK_TIMEOUT_SECONDS: 120,
|
|
4
|
+
MAX_UNION_BRANCHES: 256,
|
|
5
|
+
MAX_SCHEMA_NODES: 4096,
|
|
6
|
+
MAX_DEPTH: 32,
|
|
7
|
+
});
|
|
8
|
+
const reactiveLimits = Object.freeze({
|
|
9
|
+
MESSAGE_NAME_MAX_BYTES: 64,
|
|
10
|
+
SYNC_VALUE_DEPTH: 28,
|
|
11
|
+
});
|
|
12
|
+
const dataLimits = Object.freeze({
|
|
13
|
+
STORE_NAME_MAX_BYTES: 50,
|
|
14
|
+
MESSAGE_NAME_MAX_BYTES: 64,
|
|
15
|
+
CONFIG_NAME_MAX_BYTES: 64,
|
|
16
|
+
PERSIST_DEPTH: 28,
|
|
17
|
+
VIEW_NAME_PREFIX: "Crucible.Data.",
|
|
18
|
+
LIVE_NAME_PREFIX: "Crucible.Live.",
|
|
19
|
+
VIEW_NAME_MAX_BYTES: 64,
|
|
20
|
+
RESERVED_HANDLE_NAMES: Object.freeze(["set", "update", "insert", "remove"]),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const message = {
|
|
24
|
+
label: "messages",
|
|
25
|
+
side: "shared",
|
|
26
|
+
names: "Message",
|
|
27
|
+
maxBytes: limits.MESSAGE_NAME_MAX_BYTES,
|
|
28
|
+
typeArguments: 1,
|
|
29
|
+
arguments: 1,
|
|
30
|
+
passed: [],
|
|
31
|
+
position: "payload",
|
|
32
|
+
};
|
|
33
|
+
const replicated = {
|
|
34
|
+
label: "replicated state",
|
|
35
|
+
side: "shared",
|
|
36
|
+
names: "Replicated",
|
|
37
|
+
maxBytes: reactiveLimits.MESSAGE_NAME_MAX_BYTES,
|
|
38
|
+
typeArguments: 1,
|
|
39
|
+
arguments: 2,
|
|
40
|
+
passed: [1],
|
|
41
|
+
position: "state",
|
|
42
|
+
};
|
|
43
|
+
const config = {
|
|
44
|
+
label: "configs",
|
|
45
|
+
side: "server",
|
|
46
|
+
names: "Config",
|
|
47
|
+
maxBytes: dataLimits.CONFIG_NAME_MAX_BYTES,
|
|
48
|
+
typeArguments: 1,
|
|
49
|
+
arguments: 2,
|
|
50
|
+
passed: [1],
|
|
51
|
+
position: "persist",
|
|
52
|
+
};
|
|
53
|
+
const kinds = Object.freeze({
|
|
54
|
+
toServer: message,
|
|
55
|
+
toClient: message,
|
|
56
|
+
askServer: { ...message, typeArguments: 2, arguments: 2 },
|
|
57
|
+
replicated,
|
|
58
|
+
replicatedPerPlayer: replicated,
|
|
59
|
+
profileStore: {
|
|
60
|
+
label: "profile stores",
|
|
61
|
+
side: "shared",
|
|
62
|
+
names: "Store",
|
|
63
|
+
maxBytes: dataLimits.STORE_NAME_MAX_BYTES,
|
|
64
|
+
typeArguments: "optional",
|
|
65
|
+
arguments: 2,
|
|
66
|
+
passed: [1],
|
|
67
|
+
position: "persist",
|
|
68
|
+
store: true,
|
|
69
|
+
handles: true,
|
|
70
|
+
},
|
|
71
|
+
dataStore: {
|
|
72
|
+
label: "data stores",
|
|
73
|
+
side: "server",
|
|
74
|
+
names: "Store",
|
|
75
|
+
maxBytes: dataLimits.STORE_NAME_MAX_BYTES,
|
|
76
|
+
typeArguments: 1,
|
|
77
|
+
arguments: 2,
|
|
78
|
+
passed: [1],
|
|
79
|
+
position: "persist",
|
|
80
|
+
store: true,
|
|
81
|
+
},
|
|
82
|
+
profileMessage: {
|
|
83
|
+
label: "profile messages",
|
|
84
|
+
side: "server",
|
|
85
|
+
names: "Profile message",
|
|
86
|
+
maxBytes: dataLimits.MESSAGE_NAME_MAX_BYTES,
|
|
87
|
+
typeArguments: 1,
|
|
88
|
+
arguments: 1,
|
|
89
|
+
passed: [],
|
|
90
|
+
position: "persist",
|
|
91
|
+
},
|
|
92
|
+
config,
|
|
93
|
+
playerConfig: config,
|
|
94
|
+
});
|
|
95
|
+
const depthLimits = Object.freeze({
|
|
96
|
+
payload: limits.MAX_DEPTH,
|
|
97
|
+
state: reactiveLimits.SYNC_VALUE_DEPTH,
|
|
98
|
+
persist: dataLimits.PERSIST_DEPTH,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Marks sit only on object fields from the root, so only object fields can lose every field.
|
|
102
|
+
function replica(node) {
|
|
103
|
+
if (node.kind !== "object") return node;
|
|
104
|
+
const fields = [];
|
|
105
|
+
for (const [key, child] of node.fields) {
|
|
106
|
+
if (child.serverOnly) continue;
|
|
107
|
+
const kept = replica(child);
|
|
108
|
+
if (kept.kind === "object" && !kept.fields.length) continue;
|
|
109
|
+
fields.push([key, kept]);
|
|
110
|
+
}
|
|
111
|
+
return { kind: "object", fields };
|
|
112
|
+
}
|
|
113
|
+
// Object fields are sorted by bytes, so a depth-first walk lists paths in rule 131 order, a prefix first.
|
|
114
|
+
function serverOnlyPaths(node, prefix = []) {
|
|
115
|
+
if (node.kind !== "object") return [];
|
|
116
|
+
return node.fields.flatMap(([key, child]) =>
|
|
117
|
+
child.serverOnly ? [[...prefix, key]] : serverOnlyPaths(child, [...prefix, key]),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const SEGMENT = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
121
|
+
function viewNameProblems(store, node) {
|
|
122
|
+
if (node.kind !== "object") return [];
|
|
123
|
+
const prefix = `${dataLimits.VIEW_NAME_PREFIX}${store}.`;
|
|
124
|
+
return node.fields.flatMap(([field]) => {
|
|
125
|
+
const name = prefix + field;
|
|
126
|
+
if (!SEGMENT.test(field))
|
|
127
|
+
return [
|
|
128
|
+
[
|
|
129
|
+
`value[${JSON.stringify(field)}]`,
|
|
130
|
+
"a field the owner's client receives is named by one declaration segment, [A-Za-z][A-Za-z0-9_]*; rename it or mark it serverOnly",
|
|
131
|
+
],
|
|
132
|
+
];
|
|
133
|
+
if (Buffer.byteLength(name) > dataLimits.VIEW_NAME_MAX_BYTES)
|
|
134
|
+
return [
|
|
135
|
+
[
|
|
136
|
+
`value.${field}`,
|
|
137
|
+
`its view name ${name} is ${Buffer.byteLength(name)} bytes, over ${dataLimits.VIEW_NAME_MAX_BYTES}; this store allows field names of at most ${dataLimits.VIEW_NAME_MAX_BYTES - Buffer.byteLength(prefix)} bytes; rename the field, shorten the store name, or mark it serverOnly`,
|
|
138
|
+
],
|
|
139
|
+
];
|
|
140
|
+
return [];
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = {
|
|
145
|
+
limits,
|
|
146
|
+
reactiveLimits,
|
|
147
|
+
dataLimits,
|
|
148
|
+
kinds,
|
|
149
|
+
depthLimits,
|
|
150
|
+
replica,
|
|
151
|
+
serverOnlyPaths,
|
|
152
|
+
viewNameProblems,
|
|
153
|
+
};
|
package/transformer/index.js
CHANGED
|
@@ -2,6 +2,8 @@ const path = require("node:path");
|
|
|
2
2
|
const { createRequire } = require("node:module");
|
|
3
3
|
const { packageOf, typeId, sourceRoot } = require("./type-id.cjs");
|
|
4
4
|
const { createReporter } = require("./diagnostics.cjs");
|
|
5
|
+
const { createNetwork } = require("./network.cjs");
|
|
6
|
+
const { createDataChecks } = require("./data.cjs");
|
|
5
7
|
|
|
6
8
|
module.exports = function transformer(program, config = {}, extras = {}) {
|
|
7
9
|
const ts = extras.ts ?? require("typescript");
|
|
@@ -9,10 +11,20 @@ module.exports = function transformer(program, config = {}, extras = {}) {
|
|
|
9
11
|
const f = ts.factory;
|
|
10
12
|
const symbols = new Map();
|
|
11
13
|
const unalias = (s) => s && (s.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(s) : s);
|
|
12
|
-
|
|
14
|
+
const describe = (error) =>
|
|
15
|
+
error instanceof RangeError && error.message === "Maximum call stack size exceeded"
|
|
16
|
+
? "Cannot transform input: the type checker ran out of stack. A type in this file nests or inherits too deeply to analyse."
|
|
17
|
+
: `Cannot transform input: ${String(error instanceof Error ? error.message : error).split(/\r?\n/)[0]}`;
|
|
18
|
+
let startupFault;
|
|
13
19
|
try {
|
|
14
20
|
for (const source of program.getSourceFiles()) {
|
|
15
|
-
|
|
21
|
+
let owner;
|
|
22
|
+
try {
|
|
23
|
+
owner = packageOf(source.fileName);
|
|
24
|
+
} catch {
|
|
25
|
+
startupFault = "Cannot read package metadata. Check the project's package.json files.";
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
16
28
|
if (owner?.json.name !== "@trioforge/crucible") continue;
|
|
17
29
|
const module = checker.getSymbolAtLocation(source);
|
|
18
30
|
if (!module) continue;
|
|
@@ -21,8 +33,8 @@ module.exports = function transformer(program, config = {}, extras = {}) {
|
|
|
21
33
|
if (!symbols.has(target)) symbols.set(target, symbol.name);
|
|
22
34
|
}
|
|
23
35
|
}
|
|
24
|
-
} catch {
|
|
25
|
-
|
|
36
|
+
} catch (error) {
|
|
37
|
+
startupFault = describe(error);
|
|
26
38
|
}
|
|
27
39
|
const api = (symbol) => symbols.get(unalias(symbol));
|
|
28
40
|
const modifiers = (node, flags) => (ts.getCombinedModifierFlags(node) & flags) !== 0;
|
|
@@ -46,12 +58,14 @@ module.exports = function transformer(program, config = {}, extras = {}) {
|
|
|
46
58
|
};
|
|
47
59
|
const diagnosticSink = extras.addDiagnostic;
|
|
48
60
|
let report;
|
|
61
|
+
let networkProgram;
|
|
62
|
+
let dataProgram;
|
|
49
63
|
return (context) => (source) => {
|
|
50
64
|
if (source.isDeclarationFile) return source;
|
|
51
65
|
let used = false;
|
|
52
66
|
report ??= createReporter(ts, diagnosticSink ?? ((d) => context.addDiagnostic(d)));
|
|
53
|
-
if (
|
|
54
|
-
report(source, "Crucible",
|
|
67
|
+
if (startupFault) {
|
|
68
|
+
report(source, "Crucible", startupFault);
|
|
55
69
|
return source;
|
|
56
70
|
}
|
|
57
71
|
const isGlobal = (decl) => {
|
|
@@ -499,7 +513,11 @@ module.exports = function transformer(program, config = {}, extras = {}) {
|
|
|
499
513
|
.slice(0, -1)
|
|
500
514
|
.reverse()
|
|
501
515
|
.find((part) => ["server", "client", "shared"].includes(part));
|
|
516
|
+
let network;
|
|
517
|
+
let data;
|
|
502
518
|
const inspect = (node) => {
|
|
519
|
+
network.inspect(node);
|
|
520
|
+
data(node);
|
|
503
521
|
if (
|
|
504
522
|
ts.isImportDeclaration(node) ||
|
|
505
523
|
ts.isExportDeclaration(node) ||
|
|
@@ -622,6 +640,8 @@ module.exports = function transformer(program, config = {}, extras = {}) {
|
|
|
622
640
|
};
|
|
623
641
|
|
|
624
642
|
const visit = (node) => {
|
|
643
|
+
const networkNode = network.visit(node);
|
|
644
|
+
if (networkNode) return networkNode;
|
|
625
645
|
if (isMacro(node, "attribute")) {
|
|
626
646
|
const field = node.parent;
|
|
627
647
|
if (
|
|
@@ -694,6 +714,16 @@ module.exports = function transformer(program, config = {}, extras = {}) {
|
|
|
694
714
|
return next;
|
|
695
715
|
};
|
|
696
716
|
try {
|
|
717
|
+
networkProgram ??= createNetwork({ ts, program, checker, api, kindOf, instanceType, modifiers, isMacro });
|
|
718
|
+
network = networkProgram({
|
|
719
|
+
source,
|
|
720
|
+
report,
|
|
721
|
+
runtime,
|
|
722
|
+
side,
|
|
723
|
+
visitInput: (node) => ts.visitNode(node, visit),
|
|
724
|
+
});
|
|
725
|
+
dataProgram ??= createDataChecks({ ts, program, checker, api });
|
|
726
|
+
data = dataProgram({ report, side });
|
|
697
727
|
inspect(source);
|
|
698
728
|
const next = ts.visitEachChild(source, visit, context);
|
|
699
729
|
if (!used) return next;
|
|
@@ -704,11 +734,7 @@ module.exports = function transformer(program, config = {}, extras = {}) {
|
|
|
704
734
|
);
|
|
705
735
|
return f.updateSourceFile(next, [imports, ...next.statements]);
|
|
706
736
|
} catch (error) {
|
|
707
|
-
report(
|
|
708
|
-
source,
|
|
709
|
-
"Crucible",
|
|
710
|
-
`Cannot transform input: ${String(error instanceof Error ? error.message : error).split(/\r?\n/)[0]}`,
|
|
711
|
-
);
|
|
737
|
+
report(source, "Crucible", describe(error));
|
|
712
738
|
return source;
|
|
713
739
|
}
|
|
714
740
|
};
|