@dusted/anqst 1.0.1 → 1.5.1
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/README.md +15 -0
- package/dist/src/app.js +32 -9
- package/dist/src/base93.js +52 -0
- package/dist/src/boundary-codec-analysis.js +468 -0
- package/dist/src/boundary-codec-leaves.js +602 -0
- package/dist/src/boundary-codec-model.js +77 -0
- package/dist/src/boundary-codec-plan.js +522 -0
- package/dist/src/boundary-codec-render.js +1738 -0
- package/dist/src/boundary-codecs.js +174 -0
- package/dist/src/emit.js +1274 -150
- package/dist/src/program.js +1 -1
- package/package.json +2 -2
- package/spec/AnQst-Spec-DSL.d.ts +12 -6
package/dist/src/emit.js
CHANGED
|
@@ -13,6 +13,7 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
13
13
|
const typescript_1 = __importDefault(require("typescript"));
|
|
14
14
|
const pngjs_1 = require("pngjs");
|
|
15
15
|
const layout_1 = require("./layout");
|
|
16
|
+
const boundary_codecs_1 = require("./boundary-codecs");
|
|
16
17
|
function stripAnQstType(typeText) {
|
|
17
18
|
return typeText
|
|
18
19
|
.replace(/\bAnQst\.Type\.stringArray\b/g, "string[]")
|
|
@@ -22,6 +23,27 @@ function stripAnQstType(typeText) {
|
|
|
22
23
|
.replace(/\bAnQst\.Type\.quint64\b/g, "bigint")
|
|
23
24
|
.replace(/\bAnQst\.Type\.qint32\b/g, "number")
|
|
24
25
|
.replace(/\bAnQst\.Type\.quint32\b/g, "number")
|
|
26
|
+
.replace(/\bAnQst\.Type\.qint16\b/g, "number")
|
|
27
|
+
.replace(/\bAnQst\.Type\.quint16\b/g, "number")
|
|
28
|
+
.replace(/\bAnQst\.Type\.qint8\b/g, "number")
|
|
29
|
+
.replace(/\bAnQst\.Type\.quint8\b/g, "number")
|
|
30
|
+
.replace(/\bAnQst\.Type\.int32\b/g, "number")
|
|
31
|
+
.replace(/\bAnQst\.Type\.uint32\b/g, "number")
|
|
32
|
+
.replace(/\bAnQst\.Type\.int16\b/g, "number")
|
|
33
|
+
.replace(/\bAnQst\.Type\.uint16\b/g, "number")
|
|
34
|
+
.replace(/\bAnQst\.Type\.int8\b/g, "number")
|
|
35
|
+
.replace(/\bAnQst\.Type\.uint8\b/g, "number")
|
|
36
|
+
.replace(/\bAnQst\.Type\.buffer\b/g, "ArrayBuffer")
|
|
37
|
+
.replace(/\bAnQst\.Type\.blob\b/g, "ArrayBuffer")
|
|
38
|
+
.replace(/\bAnQst\.Type\.typedArray\b/g, "Uint8Array")
|
|
39
|
+
.replace(/\bAnQst\.Type\.uint8Array\b/g, "Uint8Array")
|
|
40
|
+
.replace(/\bAnQst\.Type\.int8Array\b/g, "Int8Array")
|
|
41
|
+
.replace(/\bAnQst\.Type\.uint16Array\b/g, "Uint16Array")
|
|
42
|
+
.replace(/\bAnQst\.Type\.int16Array\b/g, "Int16Array")
|
|
43
|
+
.replace(/\bAnQst\.Type\.uint32Array\b/g, "Uint32Array")
|
|
44
|
+
.replace(/\bAnQst\.Type\.int32Array\b/g, "Int32Array")
|
|
45
|
+
.replace(/\bAnQst\.Type\.float32Array\b/g, "Float32Array")
|
|
46
|
+
.replace(/\bAnQst\.Type\.float64Array\b/g, "Float64Array")
|
|
25
47
|
.replace(/\bAnQst\.Type\.object\b/g, "object")
|
|
26
48
|
.replace(/\bAnQst\.Type\.json\b/g, "object");
|
|
27
49
|
}
|
|
@@ -31,6 +53,129 @@ function splitGeneric(typeText) {
|
|
|
31
53
|
return null;
|
|
32
54
|
return { name: m[1], arg: m[2].trim() };
|
|
33
55
|
}
|
|
56
|
+
function filterNullishUnionTypeNodes(types) {
|
|
57
|
+
return types.filter((part) => part.kind !== typescript_1.default.SyntaxKind.NullKeyword && part.kind !== typescript_1.default.SyntaxKind.UndefinedKeyword);
|
|
58
|
+
}
|
|
59
|
+
function isStringLikeUnionTypeNode(node) {
|
|
60
|
+
return node.types.every((part) => {
|
|
61
|
+
if (typescript_1.default.isLiteralTypeNode(part) && typescript_1.default.isStringLiteral(part.literal))
|
|
62
|
+
return true;
|
|
63
|
+
if (part.kind === typescript_1.default.SyntaxKind.StringKeyword)
|
|
64
|
+
return true;
|
|
65
|
+
return false;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function isBooleanLikeUnionTypeNode(node) {
|
|
69
|
+
return node.types.every((part) => {
|
|
70
|
+
if (typescript_1.default.isLiteralTypeNode(part) && (part.literal.kind === typescript_1.default.SyntaxKind.TrueKeyword || part.literal.kind === typescript_1.default.SyntaxKind.FalseKeyword))
|
|
71
|
+
return true;
|
|
72
|
+
if (part.kind === typescript_1.default.SyntaxKind.BooleanKeyword)
|
|
73
|
+
return true;
|
|
74
|
+
return false;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function isNumberLikeUnionTypeNode(node) {
|
|
78
|
+
return node.types.every((part) => {
|
|
79
|
+
if (typescript_1.default.isLiteralTypeNode(part) && typescript_1.default.isNumericLiteral(part.literal))
|
|
80
|
+
return true;
|
|
81
|
+
if (part.kind === typescript_1.default.SyntaxKind.NumberKeyword || part.kind === typescript_1.default.SyntaxKind.BigIntKeyword)
|
|
82
|
+
return true;
|
|
83
|
+
return false;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function collectFiniteStringLiteralsTypeNode(node) {
|
|
87
|
+
const values = [];
|
|
88
|
+
for (const part of node.types) {
|
|
89
|
+
if (!typescript_1.default.isLiteralTypeNode(part) || !typescript_1.default.isStringLiteral(part.literal))
|
|
90
|
+
return null;
|
|
91
|
+
values.push(part.literal.text);
|
|
92
|
+
}
|
|
93
|
+
return values;
|
|
94
|
+
}
|
|
95
|
+
function collectFiniteBooleanLiteralsTypeNode(node) {
|
|
96
|
+
const values = [];
|
|
97
|
+
for (const part of node.types) {
|
|
98
|
+
if (!typescript_1.default.isLiteralTypeNode(part))
|
|
99
|
+
return null;
|
|
100
|
+
if (part.literal.kind === typescript_1.default.SyntaxKind.TrueKeyword) {
|
|
101
|
+
values.push(true);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (part.literal.kind === typescript_1.default.SyntaxKind.FalseKeyword) {
|
|
105
|
+
values.push(false);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
return values;
|
|
111
|
+
}
|
|
112
|
+
function collectFiniteNumberLiteralsTypeNode(node) {
|
|
113
|
+
const values = [];
|
|
114
|
+
for (const part of node.types) {
|
|
115
|
+
if (!typescript_1.default.isLiteralTypeNode(part) || !typescript_1.default.isNumericLiteral(part.literal))
|
|
116
|
+
return null;
|
|
117
|
+
values.push(Number(part.literal.text));
|
|
118
|
+
}
|
|
119
|
+
return values;
|
|
120
|
+
}
|
|
121
|
+
function finiteDomainSymbolForCpp(value) {
|
|
122
|
+
if (typeof value === "boolean")
|
|
123
|
+
return value ? "True" : "False";
|
|
124
|
+
if (typeof value === "number") {
|
|
125
|
+
const text = Number.isInteger(value) ? `${value}` : `${value}`.replace(/\./g, "_");
|
|
126
|
+
return sanitizeIdentifier(`Value_${text.replace(/-/g, "neg_")}`);
|
|
127
|
+
}
|
|
128
|
+
const direct = sanitizeIdentifier(value.trim());
|
|
129
|
+
return direct.length > 0 ? direct : "Value";
|
|
130
|
+
}
|
|
131
|
+
function buildCppFiniteDomain(primitive, values) {
|
|
132
|
+
const seen = new Set();
|
|
133
|
+
const variants = [];
|
|
134
|
+
for (const value of values) {
|
|
135
|
+
const key = `${typeof value}:${String(value)}`;
|
|
136
|
+
if (seen.has(key))
|
|
137
|
+
continue;
|
|
138
|
+
seen.add(key);
|
|
139
|
+
variants.push({
|
|
140
|
+
code: variants.length,
|
|
141
|
+
symbolicName: finiteDomainSymbolForCpp(value),
|
|
142
|
+
value
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return { primitive, variants };
|
|
146
|
+
}
|
|
147
|
+
function collectFiniteDomainFromTypeNode(typeNode) {
|
|
148
|
+
if (typescript_1.default.isParenthesizedTypeNode(typeNode)) {
|
|
149
|
+
return collectFiniteDomainFromTypeNode(typeNode.type);
|
|
150
|
+
}
|
|
151
|
+
if (typescript_1.default.isLiteralTypeNode(typeNode)) {
|
|
152
|
+
if (typescript_1.default.isStringLiteral(typeNode.literal)) {
|
|
153
|
+
return buildCppFiniteDomain("string", [typeNode.literal.text]);
|
|
154
|
+
}
|
|
155
|
+
if (typescript_1.default.isNumericLiteral(typeNode.literal)) {
|
|
156
|
+
return buildCppFiniteDomain("number", [Number(typeNode.literal.text)]);
|
|
157
|
+
}
|
|
158
|
+
if (typeNode.literal.kind === typescript_1.default.SyntaxKind.TrueKeyword || typeNode.literal.kind === typescript_1.default.SyntaxKind.FalseKeyword) {
|
|
159
|
+
return buildCppFiniteDomain("boolean", [typeNode.literal.kind === typescript_1.default.SyntaxKind.TrueKeyword]);
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
if (!typescript_1.default.isUnionTypeNode(typeNode))
|
|
164
|
+
return null;
|
|
165
|
+
const filtered = filterNullishUnionTypeNodes(typeNode.types);
|
|
166
|
+
if (filtered.length !== typeNode.types.length)
|
|
167
|
+
return null;
|
|
168
|
+
const finiteStrings = collectFiniteStringLiteralsTypeNode(typeNode);
|
|
169
|
+
if (finiteStrings)
|
|
170
|
+
return buildCppFiniteDomain("string", finiteStrings);
|
|
171
|
+
const finiteBooleans = collectFiniteBooleanLiteralsTypeNode(typeNode);
|
|
172
|
+
if (finiteBooleans)
|
|
173
|
+
return buildCppFiniteDomain("boolean", finiteBooleans);
|
|
174
|
+
const finiteNumbers = collectFiniteNumberLiteralsTypeNode(typeNode);
|
|
175
|
+
if (finiteNumbers)
|
|
176
|
+
return buildCppFiniteDomain("number", finiteNumbers);
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
34
179
|
function mapTsTypeToCpp(typeText) {
|
|
35
180
|
const raw = typeText.trim();
|
|
36
181
|
if (/\bAnQst\.Type\.qint64\b/.test(raw))
|
|
@@ -55,6 +200,9 @@ function mapTsTypeToCpp(typeText) {
|
|
|
55
200
|
return "QString";
|
|
56
201
|
if (/\bAnQst\.Type\.json\b/.test(raw) || /\bAnQst\.Type\.object\b/.test(raw))
|
|
57
202
|
return "QVariantMap";
|
|
203
|
+
if (/\bAnQst\.Type\.(?:buffer|blob|typedArray|uint8Array|int8Array|uint16Array|int16Array|uint32Array|int32Array|float32Array|float64Array)\b/.test(raw)) {
|
|
204
|
+
return "QByteArray";
|
|
205
|
+
}
|
|
58
206
|
if (/\bAnQst\.Type\.(u?int(8|16|32))\b/.test(raw)) {
|
|
59
207
|
const narrowed = raw.match(/\bAnQst\.Type\.(u?int(?:8|16|32))\b/)?.[1];
|
|
60
208
|
if (narrowed === "int8")
|
|
@@ -83,6 +231,20 @@ function mapTsTypeToCpp(typeText) {
|
|
|
83
231
|
return "void";
|
|
84
232
|
if (t === "object")
|
|
85
233
|
return "QVariantMap";
|
|
234
|
+
if (t === "ArrayBuffer")
|
|
235
|
+
return "QByteArray";
|
|
236
|
+
if ([
|
|
237
|
+
"Uint8Array",
|
|
238
|
+
"Int8Array",
|
|
239
|
+
"Uint16Array",
|
|
240
|
+
"Int16Array",
|
|
241
|
+
"Uint32Array",
|
|
242
|
+
"Int32Array",
|
|
243
|
+
"Float32Array",
|
|
244
|
+
"Float64Array"
|
|
245
|
+
].includes(t)) {
|
|
246
|
+
return "QByteArray";
|
|
247
|
+
}
|
|
86
248
|
if (t.endsWith("[]")) {
|
|
87
249
|
return `QList<${mapTsTypeToCpp(t.slice(0, -2))}>`;
|
|
88
250
|
}
|
|
@@ -110,6 +272,11 @@ function callbackName(memberName) {
|
|
|
110
272
|
function pascalCase(value) {
|
|
111
273
|
return value.length === 0 ? value : `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
|
|
112
274
|
}
|
|
275
|
+
function sanitizeIdentifier(value) {
|
|
276
|
+
const trimmed = value.replace(/[^A-Za-z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
277
|
+
const withFallback = trimmed.length > 0 ? trimmed : "Codec";
|
|
278
|
+
return /^[0-9]/.test(withFallback) ? `T_${withFallback}` : withFallback;
|
|
279
|
+
}
|
|
113
280
|
function variantToCppExpression(cppType, expr) {
|
|
114
281
|
if (cppType === "QString")
|
|
115
282
|
return `${expr}.toString()`;
|
|
@@ -117,6 +284,8 @@ function variantToCppExpression(cppType, expr) {
|
|
|
117
284
|
return `${expr}.toStringList()`;
|
|
118
285
|
if (cppType === "QVariantMap")
|
|
119
286
|
return `${expr}.toMap()`;
|
|
287
|
+
if (cppType === "QByteArray")
|
|
288
|
+
return `${expr}.toByteArray()`;
|
|
120
289
|
if (cppType === "double")
|
|
121
290
|
return `${expr}.toDouble()`;
|
|
122
291
|
if (cppType === "bool")
|
|
@@ -140,6 +309,7 @@ function cppToVariantExpression(cppType, expr) {
|
|
|
140
309
|
if (cppType === "QString" ||
|
|
141
310
|
cppType === "QStringList" ||
|
|
142
311
|
cppType === "QVariantMap" ||
|
|
312
|
+
cppType === "QByteArray" ||
|
|
143
313
|
cppType === "double" ||
|
|
144
314
|
cppType === "bool" ||
|
|
145
315
|
cppType === "qint64" ||
|
|
@@ -398,6 +568,8 @@ class CppTypeNormalizer {
|
|
|
398
568
|
this.seedOrder = [];
|
|
399
569
|
this.allKnownNames = new Set();
|
|
400
570
|
this.usedNames = new Set();
|
|
571
|
+
this.syntheticNameByKey = new Map();
|
|
572
|
+
this.finiteDomainNameByKey = new Map();
|
|
401
573
|
for (const decl of collectStructDecls(spec)) {
|
|
402
574
|
this.allKnownNames.add(decl.name);
|
|
403
575
|
this.usedNames.add(decl.name);
|
|
@@ -421,9 +593,11 @@ class CppTypeNormalizer {
|
|
|
421
593
|
const order = this.topologicalOrder();
|
|
422
594
|
const orderedDecls = order.map((name) => this.declMap.get(name)).filter((x) => !!x);
|
|
423
595
|
const structNames = orderedDecls.filter((d) => d.kind === "struct").map((d) => d.name);
|
|
596
|
+
const metatypeNames = orderedDecls.filter((d) => d.kind === "struct" || d.kind === "enum").map((d) => d.name);
|
|
424
597
|
return {
|
|
425
598
|
orderedDecls,
|
|
426
599
|
structNames,
|
|
600
|
+
metatypeNames,
|
|
427
601
|
mapTypeText: (typeText, nameHintParts) => this.mapTypeText(typeText, nameHintParts)
|
|
428
602
|
};
|
|
429
603
|
}
|
|
@@ -441,7 +615,19 @@ class CppTypeNormalizer {
|
|
|
441
615
|
optional: !!member.questionToken
|
|
442
616
|
});
|
|
443
617
|
}
|
|
444
|
-
return { name, kind: "struct", fields, aliasType: null, deps, isUnionAlias: false };
|
|
618
|
+
return { name, kind: "struct", fields, aliasType: null, deps, isUnionAlias: false, finiteDomain: null };
|
|
619
|
+
}
|
|
620
|
+
const finiteDomain = collectFiniteDomainFromTypeNode(node.type);
|
|
621
|
+
if (finiteDomain) {
|
|
622
|
+
return {
|
|
623
|
+
name,
|
|
624
|
+
kind: "enum",
|
|
625
|
+
fields: [],
|
|
626
|
+
aliasType: null,
|
|
627
|
+
deps: new Set(),
|
|
628
|
+
isUnionAlias: false,
|
|
629
|
+
finiteDomain
|
|
630
|
+
};
|
|
445
631
|
}
|
|
446
632
|
const deps = new Set();
|
|
447
633
|
const aliasType = this.mapTypeNode(node.type, [name], deps);
|
|
@@ -451,14 +637,29 @@ class CppTypeNormalizer {
|
|
|
451
637
|
fields: [],
|
|
452
638
|
aliasType,
|
|
453
639
|
deps,
|
|
454
|
-
isUnionAlias: node.type.getText().includes("|")
|
|
640
|
+
isUnionAlias: node.type.getText().includes("|"),
|
|
641
|
+
finiteDomain: null
|
|
455
642
|
};
|
|
456
643
|
}
|
|
457
644
|
mapTypeNode(typeNode, nameHintParts, deps) {
|
|
458
645
|
if (typescript_1.default.isParenthesizedTypeNode(typeNode)) {
|
|
459
646
|
return this.mapTypeNode(typeNode.type, nameHintParts, deps);
|
|
460
647
|
}
|
|
648
|
+
const finiteDomain = collectFiniteDomainFromTypeNode(typeNode);
|
|
649
|
+
if (finiteDomain) {
|
|
650
|
+
return this.ensureFiniteDomainType(finiteDomain, nameHintParts, deps);
|
|
651
|
+
}
|
|
461
652
|
if (typescript_1.default.isUnionTypeNode(typeNode)) {
|
|
653
|
+
const filtered = filterNullishUnionTypeNodes(typeNode.types);
|
|
654
|
+
if (filtered.length === 1) {
|
|
655
|
+
return this.mapTypeNode(filtered[0], nameHintParts, deps);
|
|
656
|
+
}
|
|
657
|
+
if (isStringLikeUnionTypeNode(typeNode))
|
|
658
|
+
return "QString";
|
|
659
|
+
if (isBooleanLikeUnionTypeNode(typeNode))
|
|
660
|
+
return "bool";
|
|
661
|
+
if (isNumberLikeUnionTypeNode(typeNode))
|
|
662
|
+
return "double";
|
|
462
663
|
return "QString";
|
|
463
664
|
}
|
|
464
665
|
if (typescript_1.default.isTypeLiteralNode(typeNode)) {
|
|
@@ -504,9 +705,44 @@ class CppTypeNormalizer {
|
|
|
504
705
|
this.collectKnownTypeDeps(mapped, deps);
|
|
505
706
|
return mapped;
|
|
506
707
|
}
|
|
708
|
+
ensureFiniteDomainType(domain, nameHintParts, deps) {
|
|
709
|
+
const baseName = this.makeSyntheticBaseName(nameHintParts);
|
|
710
|
+
const domainKey = `${baseName}::finite::${domain.primitive}::${domain.variants.map((variant) => `${variant.symbolicName}=${String(variant.value)}`).join("|")}`;
|
|
711
|
+
const existingName = this.finiteDomainNameByKey.get(domainKey);
|
|
712
|
+
if (existingName) {
|
|
713
|
+
deps.add(existingName);
|
|
714
|
+
return existingName;
|
|
715
|
+
}
|
|
716
|
+
const synthesizedName = this.allocateUniqueName(baseName);
|
|
717
|
+
this.finiteDomainNameByKey.set(domainKey, synthesizedName);
|
|
718
|
+
if (this.declMap.has(synthesizedName)) {
|
|
719
|
+
deps.add(synthesizedName);
|
|
720
|
+
return synthesizedName;
|
|
721
|
+
}
|
|
722
|
+
this.allKnownNames.add(synthesizedName);
|
|
723
|
+
this.declMap.set(synthesizedName, {
|
|
724
|
+
name: synthesizedName,
|
|
725
|
+
kind: "enum",
|
|
726
|
+
fields: [],
|
|
727
|
+
aliasType: null,
|
|
728
|
+
deps: new Set(),
|
|
729
|
+
isUnionAlias: false,
|
|
730
|
+
finiteDomain: domain
|
|
731
|
+
});
|
|
732
|
+
this.seedOrder.push(synthesizedName);
|
|
733
|
+
deps.add(synthesizedName);
|
|
734
|
+
return synthesizedName;
|
|
735
|
+
}
|
|
507
736
|
ensureSyntheticStruct(typeNode, nameHintParts, deps) {
|
|
508
737
|
const baseName = this.makeSyntheticBaseName(nameHintParts);
|
|
738
|
+
const syntheticKey = `${baseName}::${typeNode.getText()}`;
|
|
739
|
+
const existingName = this.syntheticNameByKey.get(syntheticKey);
|
|
740
|
+
if (existingName) {
|
|
741
|
+
deps.add(existingName);
|
|
742
|
+
return existingName;
|
|
743
|
+
}
|
|
509
744
|
const synthesizedName = this.allocateUniqueName(baseName);
|
|
745
|
+
this.syntheticNameByKey.set(syntheticKey, synthesizedName);
|
|
510
746
|
if (this.declMap.has(synthesizedName)) {
|
|
511
747
|
deps.add(synthesizedName);
|
|
512
748
|
return synthesizedName;
|
|
@@ -530,7 +766,8 @@ class CppTypeNormalizer {
|
|
|
530
766
|
fields,
|
|
531
767
|
aliasType: null,
|
|
532
768
|
deps: localDeps,
|
|
533
|
-
isUnionAlias: false
|
|
769
|
+
isUnionAlias: false,
|
|
770
|
+
finiteDomain: null
|
|
534
771
|
});
|
|
535
772
|
this.seedOrder.push(synthesizedName);
|
|
536
773
|
deps.add(synthesizedName);
|
|
@@ -592,6 +829,17 @@ class CppTypeNormalizer {
|
|
|
592
829
|
}
|
|
593
830
|
}
|
|
594
831
|
function renderCppDecl(decl) {
|
|
832
|
+
if (decl.kind === "enum") {
|
|
833
|
+
const variants = decl.finiteDomain?.variants ?? [];
|
|
834
|
+
const underlyingType = variants.length <= 0xff ? "std::uint8_t" : variants.length <= 0xffff ? "std::uint16_t" : "std::uint32_t";
|
|
835
|
+
const lines = [];
|
|
836
|
+
lines.push(`enum class ${decl.name} : ${underlyingType} {`);
|
|
837
|
+
for (const variant of variants) {
|
|
838
|
+
lines.push(` ${variant.symbolicName} = ${variant.code},`);
|
|
839
|
+
}
|
|
840
|
+
lines.push("};");
|
|
841
|
+
return lines.join("\n");
|
|
842
|
+
}
|
|
595
843
|
if (decl.kind === "alias") {
|
|
596
844
|
if (decl.isUnionAlias && decl.aliasType === "QString") {
|
|
597
845
|
return `using ${decl.name} = QString; // union mapped conservatively`;
|
|
@@ -647,9 +895,247 @@ function collectDragDropMimeConstants(spec) {
|
|
|
647
895
|
}
|
|
648
896
|
return constants;
|
|
649
897
|
}
|
|
898
|
+
function createCarrierSummary(counts, singleKinds = [], mayBlob = false, mustBlob = false) {
|
|
899
|
+
return {
|
|
900
|
+
counts: new Set(counts),
|
|
901
|
+
singleKinds: new Set(singleKinds),
|
|
902
|
+
mayBlob,
|
|
903
|
+
mustBlob
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
function addOptionalAbsence(summary) {
|
|
907
|
+
const counts = new Set(summary.counts);
|
|
908
|
+
counts.add(0);
|
|
909
|
+
return {
|
|
910
|
+
counts,
|
|
911
|
+
singleKinds: new Set(summary.singleKinds),
|
|
912
|
+
mayBlob: summary.mayBlob,
|
|
913
|
+
mustBlob: summary.mustBlob
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
function saturatingItemCountAdd(left, right) {
|
|
917
|
+
if (left === 2 || right === 2)
|
|
918
|
+
return 2;
|
|
919
|
+
const total = left + right;
|
|
920
|
+
return total >= 2 ? 2 : total;
|
|
921
|
+
}
|
|
922
|
+
function mergeCarrierSummaries(left, right) {
|
|
923
|
+
const counts = new Set();
|
|
924
|
+
const singleKinds = new Set();
|
|
925
|
+
for (const leftCount of left.counts) {
|
|
926
|
+
for (const rightCount of right.counts) {
|
|
927
|
+
const total = saturatingItemCountAdd(leftCount, rightCount);
|
|
928
|
+
counts.add(total);
|
|
929
|
+
if (total !== 1)
|
|
930
|
+
continue;
|
|
931
|
+
if (leftCount === 1 && rightCount === 0) {
|
|
932
|
+
for (const kind of left.singleKinds)
|
|
933
|
+
singleKinds.add(kind);
|
|
934
|
+
}
|
|
935
|
+
if (leftCount === 0 && rightCount === 1) {
|
|
936
|
+
for (const kind of right.singleKinds)
|
|
937
|
+
singleKinds.add(kind);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
return {
|
|
942
|
+
counts,
|
|
943
|
+
singleKinds,
|
|
944
|
+
mayBlob: left.mayBlob || right.mayBlob,
|
|
945
|
+
mustBlob: left.mustBlob || right.mustBlob
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
function summarizePlanCarrier(node) {
|
|
949
|
+
switch (node.nodeKind) {
|
|
950
|
+
case "leaf":
|
|
951
|
+
if (node.blobEntryId)
|
|
952
|
+
return createCarrierSummary([0], [], true, true);
|
|
953
|
+
if (node.itemEntryId) {
|
|
954
|
+
return createCarrierSummary([1], [node.leaf.region === "dynamic" ? "object" : "string"]);
|
|
955
|
+
}
|
|
956
|
+
return createCarrierSummary([0]);
|
|
957
|
+
case "named":
|
|
958
|
+
return summarizePlanCarrier(node.target);
|
|
959
|
+
case "finite-domain":
|
|
960
|
+
if (node.blobEntryId)
|
|
961
|
+
return createCarrierSummary([0], [], true, true);
|
|
962
|
+
if (node.itemEntryId)
|
|
963
|
+
return createCarrierSummary([1], ["string"]);
|
|
964
|
+
return createCarrierSummary([0]);
|
|
965
|
+
case "array": {
|
|
966
|
+
if (node.extentStrategy === "blob-tail") {
|
|
967
|
+
return createCarrierSummary([0], [], true, false);
|
|
968
|
+
}
|
|
969
|
+
const elementSummary = summarizePlanCarrier(node.element);
|
|
970
|
+
const counts = new Set([0]);
|
|
971
|
+
const singleKinds = new Set();
|
|
972
|
+
if (elementSummary.counts.has(1)) {
|
|
973
|
+
counts.add(1);
|
|
974
|
+
for (const kind of elementSummary.singleKinds)
|
|
975
|
+
singleKinds.add(kind);
|
|
976
|
+
}
|
|
977
|
+
if (elementSummary.counts.has(1) || elementSummary.counts.has(2)) {
|
|
978
|
+
counts.add(2);
|
|
979
|
+
}
|
|
980
|
+
return {
|
|
981
|
+
counts,
|
|
982
|
+
singleKinds,
|
|
983
|
+
mayBlob: true,
|
|
984
|
+
mustBlob: true
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
case "struct": {
|
|
988
|
+
let summary = createCarrierSummary([0]);
|
|
989
|
+
for (const field of node.fields) {
|
|
990
|
+
let fieldSummary = summarizePlanCarrier(field.node);
|
|
991
|
+
if (field.optional) {
|
|
992
|
+
fieldSummary = addOptionalAbsence(fieldSummary);
|
|
993
|
+
}
|
|
994
|
+
if (field.presenceStrategy) {
|
|
995
|
+
fieldSummary = {
|
|
996
|
+
counts: new Set(fieldSummary.counts),
|
|
997
|
+
singleKinds: new Set(fieldSummary.singleKinds),
|
|
998
|
+
mayBlob: true,
|
|
999
|
+
mustBlob: true
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
summary = mergeCarrierSummaries(summary, fieldSummary);
|
|
1003
|
+
}
|
|
1004
|
+
return summary;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
function inferDragDropCarrierKinds(plan) {
|
|
1009
|
+
const summary = summarizePlanCarrier(plan.root);
|
|
1010
|
+
const carriers = new Set();
|
|
1011
|
+
const addNoBlobCarriers = () => {
|
|
1012
|
+
if (summary.counts.has(0))
|
|
1013
|
+
carriers.add("array");
|
|
1014
|
+
if (summary.counts.has(1)) {
|
|
1015
|
+
for (const kind of summary.singleKinds)
|
|
1016
|
+
carriers.add(kind);
|
|
1017
|
+
}
|
|
1018
|
+
if (summary.counts.has(2))
|
|
1019
|
+
carriers.add("array");
|
|
1020
|
+
};
|
|
1021
|
+
if (summary.mustBlob) {
|
|
1022
|
+
if (summary.counts.has(0))
|
|
1023
|
+
carriers.add("string");
|
|
1024
|
+
if (summary.counts.has(1) || summary.counts.has(2))
|
|
1025
|
+
carriers.add("array");
|
|
1026
|
+
}
|
|
1027
|
+
else {
|
|
1028
|
+
addNoBlobCarriers();
|
|
1029
|
+
if (summary.mayBlob) {
|
|
1030
|
+
if (summary.counts.has(0))
|
|
1031
|
+
carriers.add("string");
|
|
1032
|
+
if (summary.counts.has(1) || summary.counts.has(2))
|
|
1033
|
+
carriers.add("array");
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
return ["string", "array", "object"].filter((kind) => carriers.has(kind));
|
|
1037
|
+
}
|
|
1038
|
+
function collectDragDropPayloadHelpers(spec, cppTypes, cppCodecCatalog) {
|
|
1039
|
+
const seen = new Set();
|
|
1040
|
+
const helpers = [];
|
|
1041
|
+
for (const service of spec.services) {
|
|
1042
|
+
for (const member of service.members) {
|
|
1043
|
+
if ((member.kind !== "DropTarget" && member.kind !== "HoverTarget") || !member.payloadTypeText)
|
|
1044
|
+
continue;
|
|
1045
|
+
const typeName = member.payloadTypeText.replace(/\s/g, "");
|
|
1046
|
+
if (seen.has(typeName))
|
|
1047
|
+
continue;
|
|
1048
|
+
seen.add(typeName);
|
|
1049
|
+
const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(cppCodecCatalog, service.name, member.name);
|
|
1050
|
+
if (!payloadSite)
|
|
1051
|
+
continue;
|
|
1052
|
+
const plan = cppCodecCatalog.plansByCodecId.get(payloadSite.codecId);
|
|
1053
|
+
if (!plan)
|
|
1054
|
+
continue;
|
|
1055
|
+
helpers.push({
|
|
1056
|
+
typeName,
|
|
1057
|
+
cppType: cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]),
|
|
1058
|
+
codecId: payloadSite.codecId,
|
|
1059
|
+
carriers: inferDragDropCarrierKinds(plan)
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
return helpers;
|
|
1064
|
+
}
|
|
1065
|
+
function collectTsDragDropPayloadHelpers(spec, codecCatalog) {
|
|
1066
|
+
const seen = new Set();
|
|
1067
|
+
const helpers = [];
|
|
1068
|
+
for (const service of spec.services) {
|
|
1069
|
+
for (const member of service.members) {
|
|
1070
|
+
if ((member.kind !== "DropTarget" && member.kind !== "HoverTarget") || !member.payloadTypeText)
|
|
1071
|
+
continue;
|
|
1072
|
+
const typeName = member.payloadTypeText.replace(/\s/g, "");
|
|
1073
|
+
if (seen.has(typeName))
|
|
1074
|
+
continue;
|
|
1075
|
+
seen.add(typeName);
|
|
1076
|
+
const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(codecCatalog, service.name, member.name);
|
|
1077
|
+
if (!payloadSite)
|
|
1078
|
+
continue;
|
|
1079
|
+
const plan = codecCatalog.plansByCodecId.get(payloadSite.codecId);
|
|
1080
|
+
if (!plan)
|
|
1081
|
+
continue;
|
|
1082
|
+
helpers.push({
|
|
1083
|
+
typeName,
|
|
1084
|
+
tsType: mapTypeTextToTs(member.payloadTypeText),
|
|
1085
|
+
codecId: payloadSite.codecId,
|
|
1086
|
+
carriers: inferDragDropCarrierKinds(plan)
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
return helpers;
|
|
1091
|
+
}
|
|
1092
|
+
function renderTsDragDropPayloadHelpers(spec, codecCatalog) {
|
|
1093
|
+
const helpers = collectTsDragDropPayloadHelpers(spec, codecCatalog);
|
|
1094
|
+
return helpers
|
|
1095
|
+
.map((helper) => {
|
|
1096
|
+
const lines = [
|
|
1097
|
+
`function decodeDragDropPayload_${helper.typeName}(rawPayload: unknown): ${helper.tsType} {`,
|
|
1098
|
+
` if (typeof rawPayload !== "string") {`,
|
|
1099
|
+
` throw new Error("Drag/drop payload must be tagged text.");`,
|
|
1100
|
+
` }`,
|
|
1101
|
+
` if (rawPayload.length === 0) {`,
|
|
1102
|
+
` throw new Error("Drag/drop payload is empty.");`,
|
|
1103
|
+
` }`,
|
|
1104
|
+
` const transportTag = rawPayload[0];`,
|
|
1105
|
+
` const payloadText = rawPayload.slice(1);`
|
|
1106
|
+
];
|
|
1107
|
+
if (helper.carriers.includes("string")) {
|
|
1108
|
+
lines.push(` if (transportTag === "S") {`);
|
|
1109
|
+
lines.push(` return decode${helper.codecId}(payloadText);`);
|
|
1110
|
+
lines.push(` }`);
|
|
1111
|
+
}
|
|
1112
|
+
if (helper.carriers.includes("array")) {
|
|
1113
|
+
lines.push(` if (transportTag === "A") {`);
|
|
1114
|
+
lines.push(` const parsed = JSON.parse(payloadText) as unknown;`);
|
|
1115
|
+
lines.push(` if (!Array.isArray(parsed)) {`);
|
|
1116
|
+
lines.push(` throw new Error("Drag/drop payload must be a JSON array.");`);
|
|
1117
|
+
lines.push(` }`);
|
|
1118
|
+
lines.push(` return decode${helper.codecId}(parsed);`);
|
|
1119
|
+
lines.push(` }`);
|
|
1120
|
+
}
|
|
1121
|
+
if (helper.carriers.includes("object")) {
|
|
1122
|
+
lines.push(` if (transportTag === "O") {`);
|
|
1123
|
+
lines.push(` const parsed = JSON.parse(payloadText) as unknown;`);
|
|
1124
|
+
lines.push(` if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {`);
|
|
1125
|
+
lines.push(` throw new Error("Drag/drop payload must be a JSON object.");`);
|
|
1126
|
+
lines.push(` }`);
|
|
1127
|
+
lines.push(` return decode${helper.codecId}(parsed);`);
|
|
1128
|
+
lines.push(` }`);
|
|
1129
|
+
}
|
|
1130
|
+
lines.push(` throw new Error(\`Drag/drop payload has an unknown transport tag: \${transportTag}\`);`);
|
|
1131
|
+
lines.push(`}`);
|
|
1132
|
+
return lines.join("\n");
|
|
1133
|
+
})
|
|
1134
|
+
.join("\n\n");
|
|
1135
|
+
}
|
|
650
1136
|
function renderTypesHeader(spec, cppTypes) {
|
|
651
1137
|
const decls = cppTypes.orderedDecls.map(renderCppDecl).join("\n\n");
|
|
652
|
-
const metatypes = cppTypes.
|
|
1138
|
+
const metatypes = cppTypes.metatypeNames
|
|
653
1139
|
.flatMap((name) => [
|
|
654
1140
|
`Q_DECLARE_METATYPE(${spec.widgetName}::${name})`,
|
|
655
1141
|
`Q_DECLARE_METATYPE(QList<${spec.widgetName}::${name}>)`
|
|
@@ -663,6 +1149,7 @@ function renderTypesHeader(spec, cppTypes) {
|
|
|
663
1149
|
return `#pragma once
|
|
664
1150
|
#include <QString>
|
|
665
1151
|
#include <QStringList>
|
|
1152
|
+
#include <QByteArray>
|
|
666
1153
|
#include <QList>
|
|
667
1154
|
#include <QVariantMap>
|
|
668
1155
|
#include <QMetaType>
|
|
@@ -680,13 +1167,13 @@ ${metatypes}
|
|
|
680
1167
|
}
|
|
681
1168
|
function renderWidgetUmbrellaHeader(spec) {
|
|
682
1169
|
return `#pragma once
|
|
683
|
-
// Built by <AnQst_version>
|
|
684
1170
|
#include "${spec.widgetName}Widget.h"
|
|
685
1171
|
#include "${spec.widgetName}Types.h"
|
|
686
1172
|
`;
|
|
687
1173
|
}
|
|
688
|
-
function renderWidgetHeader(spec, cppTypes) {
|
|
1174
|
+
function renderWidgetHeader(spec, cppTypes, cppCodecCatalog) {
|
|
689
1175
|
const widgetClassName = `${spec.widgetName}Widget`;
|
|
1176
|
+
const dragDropPayloadHelpers = collectDragDropPayloadHelpers(spec, cppTypes, cppCodecCatalog);
|
|
690
1177
|
const callbackAliases = [];
|
|
691
1178
|
const publicMethods = [];
|
|
692
1179
|
const slotMethods = [];
|
|
@@ -696,10 +1183,12 @@ function renderWidgetHeader(spec, cppTypes) {
|
|
|
696
1183
|
const properties = [];
|
|
697
1184
|
const fields = [];
|
|
698
1185
|
const publicSlots = [];
|
|
699
|
-
const
|
|
1186
|
+
const dragDropHelperMethods = dragDropPayloadHelpers.flatMap((helper) => [
|
|
1187
|
+
`static QByteArray encodeDragDropPayload_${helper.typeName}(const ${helper.cppType}& payload);`,
|
|
1188
|
+
`static std::optional<${helper.cppType}> decodeDragDropPayload_${helper.typeName}(const QByteArray& rawPayload);`
|
|
1189
|
+
]);
|
|
700
1190
|
for (const service of spec.services) {
|
|
701
1191
|
for (const member of service.members) {
|
|
702
|
-
bindings.push({ service: service.name, member: member.name, kind: member.kind });
|
|
703
1192
|
const memberPascal = pascalCase(member.name);
|
|
704
1193
|
if (member.kind === "Call" && member.payloadTypeText) {
|
|
705
1194
|
const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
|
|
@@ -747,6 +1236,7 @@ function renderWidgetHeader(spec, cppTypes) {
|
|
|
747
1236
|
}
|
|
748
1237
|
}
|
|
749
1238
|
return `#pragma once
|
|
1239
|
+
#include <QByteArray>
|
|
750
1240
|
#include <QDateTime>
|
|
751
1241
|
#include <QHash>
|
|
752
1242
|
#include <QMetaMethod>
|
|
@@ -754,6 +1244,7 @@ function renderWidgetHeader(spec, cppTypes) {
|
|
|
754
1244
|
#include <QVariant>
|
|
755
1245
|
#include <QVariantList>
|
|
756
1246
|
#include <functional>
|
|
1247
|
+
#include <optional>
|
|
757
1248
|
#include "AnQstWebHostBase.h"
|
|
758
1249
|
#include "${spec.widgetName}Types.h"
|
|
759
1250
|
|
|
@@ -784,6 +1275,7 @@ ${handleMethods.join("\n")}
|
|
|
784
1275
|
static constexpr const char* kBootstrapContentRoot = "qrc:/${spec.widgetName.toLowerCase()}";
|
|
785
1276
|
static constexpr const char* kBootstrapBridgeObject = "${spec.widgetName}Bridge";
|
|
786
1277
|
static constexpr int kMaxQueuedCallsPerEndpoint = 1024;
|
|
1278
|
+
${dragDropHelperMethods.map((s) => ` ${s}`).join("\n")}
|
|
787
1279
|
|
|
788
1280
|
handle handle;
|
|
789
1281
|
${publicMethods.map((s) => ` ${s}`).join("\n")}
|
|
@@ -806,13 +1298,6 @@ private:
|
|
|
806
1298
|
QVariantList args;
|
|
807
1299
|
QDateTime enqueuedAt;
|
|
808
1300
|
};
|
|
809
|
-
struct BridgeBindingRow {
|
|
810
|
-
const char* service;
|
|
811
|
-
const char* member;
|
|
812
|
-
const char* kind;
|
|
813
|
-
};
|
|
814
|
-
static const BridgeBindingRow kBridgeBindings[];
|
|
815
|
-
static constexpr int kBridgeBindingsCount = ${bindings.length};
|
|
816
1301
|
static QString makeBindingKey(const QString& service, const QString& member);
|
|
817
1302
|
void installBridgeBindings();
|
|
818
1303
|
bool hasEmitterListeners(const QString& service, const QString& member) const;
|
|
@@ -834,15 +1319,25 @@ ${fields.map((f) => ` ${f}`).join("\n")}
|
|
|
834
1319
|
};
|
|
835
1320
|
`;
|
|
836
1321
|
}
|
|
837
|
-
function renderCppStub(spec, cppTypes) {
|
|
1322
|
+
function renderCppStub(spec, cppTypes, cppCodecCatalog) {
|
|
838
1323
|
const widgetClassName = `${spec.widgetName}Widget`;
|
|
1324
|
+
const dragDropPayloadHelpers = collectDragDropPayloadHelpers(spec, cppTypes, cppCodecCatalog);
|
|
1325
|
+
const cppCodecHelpers = (0, boundary_codecs_1.renderCppBoundaryCodecHelpers)(cppCodecCatalog, (typeText, pathHintParts) => cppTypes.mapTypeText(typeText, pathHintParts)).trim();
|
|
839
1326
|
const lines = [];
|
|
840
1327
|
lines.push(`#include "include/${spec.widgetName}Widget.h"`);
|
|
1328
|
+
lines.push(`#include "AnQstBase93.h"`);
|
|
841
1329
|
lines.push(`#include <QDebug>`);
|
|
842
1330
|
lines.push(`#include <QElapsedTimer>`);
|
|
843
1331
|
lines.push(`#include <QEventLoop>`);
|
|
1332
|
+
lines.push(`#include <QJsonArray>`);
|
|
1333
|
+
lines.push(`#include <QJsonDocument>`);
|
|
1334
|
+
lines.push(`#include <QJsonObject>`);
|
|
844
1335
|
lines.push(`#include <QMetaType>`);
|
|
845
1336
|
lines.push(`#include <QTimer>`);
|
|
1337
|
+
lines.push(`#include <cstring>`);
|
|
1338
|
+
lines.push(`#include <cstdint>`);
|
|
1339
|
+
lines.push(`#include <string>`);
|
|
1340
|
+
lines.push(`#include <vector>`);
|
|
846
1341
|
lines.push(`#include <stdexcept>`);
|
|
847
1342
|
lines.push("");
|
|
848
1343
|
lines.push(`using namespace ${spec.widgetName};`);
|
|
@@ -852,7 +1347,7 @@ function renderCppStub(spec, cppTypes) {
|
|
|
852
1347
|
lines.push("namespace {");
|
|
853
1348
|
lines.push("void registerGeneratedMetaTypes() {");
|
|
854
1349
|
lines.push(" static const bool registered = []() {");
|
|
855
|
-
for (const typeName of cppTypes.
|
|
1350
|
+
for (const typeName of cppTypes.metatypeNames) {
|
|
856
1351
|
lines.push(` qRegisterMetaType<${spec.widgetName}::${typeName}>("${spec.widgetName}::${typeName}");`);
|
|
857
1352
|
lines.push(` qRegisterMetaType<QList<${spec.widgetName}::${typeName}>>("QList<${spec.widgetName}::${typeName}>");`);
|
|
858
1353
|
}
|
|
@@ -860,8 +1355,89 @@ function renderCppStub(spec, cppTypes) {
|
|
|
860
1355
|
lines.push(" }();");
|
|
861
1356
|
lines.push(" Q_UNUSED(registered);");
|
|
862
1357
|
lines.push("}");
|
|
1358
|
+
if (cppCodecHelpers.length > 0) {
|
|
1359
|
+
lines.push("");
|
|
1360
|
+
lines.push(cppCodecHelpers);
|
|
1361
|
+
}
|
|
863
1362
|
lines.push("}");
|
|
864
1363
|
lines.push("");
|
|
1364
|
+
for (const helper of dragDropPayloadHelpers) {
|
|
1365
|
+
lines.push(`QByteArray ${widgetClassName}::encodeDragDropPayload_${helper.typeName}(const ${helper.cppType}& payload) {`);
|
|
1366
|
+
lines.push(` const QVariant wire = encode${helper.codecId}(payload);`);
|
|
1367
|
+
if (helper.carriers.includes("string")) {
|
|
1368
|
+
lines.push(` if (wire.type() == QVariant::String) {`);
|
|
1369
|
+
lines.push(` QByteArray out;`);
|
|
1370
|
+
lines.push(` out.append('S');`);
|
|
1371
|
+
lines.push(` out.append(wire.toString().toUtf8());`);
|
|
1372
|
+
lines.push(` return out;`);
|
|
1373
|
+
lines.push(` }`);
|
|
1374
|
+
}
|
|
1375
|
+
if (helper.carriers.includes("array")) {
|
|
1376
|
+
lines.push(` if (wire.type() == QVariant::List) {`);
|
|
1377
|
+
lines.push(` QByteArray out;`);
|
|
1378
|
+
lines.push(` out.append('A');`);
|
|
1379
|
+
lines.push(` out.append(QJsonDocument(QJsonArray::fromVariantList(wire.toList())).toJson(QJsonDocument::Compact));`);
|
|
1380
|
+
lines.push(` return out;`);
|
|
1381
|
+
lines.push(` }`);
|
|
1382
|
+
}
|
|
1383
|
+
if (helper.carriers.includes("object")) {
|
|
1384
|
+
lines.push(` if (wire.type() == QVariant::Map) {`);
|
|
1385
|
+
lines.push(` QByteArray out;`);
|
|
1386
|
+
lines.push(` out.append('O');`);
|
|
1387
|
+
lines.push(` out.append(QJsonDocument(QJsonObject::fromVariantMap(wire.toMap())).toJson(QJsonDocument::Compact));`);
|
|
1388
|
+
lines.push(` return out;`);
|
|
1389
|
+
lines.push(` }`);
|
|
1390
|
+
}
|
|
1391
|
+
lines.push(` throw std::runtime_error("AnQst drag/drop payload codec emitted an unsupported top-level carrier.");`);
|
|
1392
|
+
lines.push(`}`);
|
|
1393
|
+
lines.push("");
|
|
1394
|
+
lines.push(`std::optional<${helper.cppType}> ${widgetClassName}::decodeDragDropPayload_${helper.typeName}(const QByteArray& rawPayload) {`);
|
|
1395
|
+
lines.push(` if (rawPayload.isEmpty()) {`);
|
|
1396
|
+
lines.push(` return std::nullopt;`);
|
|
1397
|
+
lines.push(` }`);
|
|
1398
|
+
lines.push(` const char transportTag = rawPayload.at(0);`);
|
|
1399
|
+
lines.push(` const QByteArray payloadBytes = rawPayload.mid(1);`);
|
|
1400
|
+
if (helper.carriers.includes("string")) {
|
|
1401
|
+
lines.push(` if (transportTag == 'S') {`);
|
|
1402
|
+
lines.push(` try {`);
|
|
1403
|
+
lines.push(` return decode${helper.codecId}(QString::fromUtf8(payloadBytes));`);
|
|
1404
|
+
lines.push(` } catch (...) {`);
|
|
1405
|
+
lines.push(` return std::nullopt;`);
|
|
1406
|
+
lines.push(` }`);
|
|
1407
|
+
lines.push(` }`);
|
|
1408
|
+
}
|
|
1409
|
+
if (helper.carriers.includes("array")) {
|
|
1410
|
+
lines.push(` if (transportTag == 'A') {`);
|
|
1411
|
+
lines.push(` QJsonParseError parseError;`);
|
|
1412
|
+
lines.push(` const QJsonDocument document = QJsonDocument::fromJson(payloadBytes, &parseError);`);
|
|
1413
|
+
lines.push(` if (parseError.error != QJsonParseError::NoError || !document.isArray()) {`);
|
|
1414
|
+
lines.push(` return std::nullopt;`);
|
|
1415
|
+
lines.push(` }`);
|
|
1416
|
+
lines.push(` try {`);
|
|
1417
|
+
lines.push(` return decode${helper.codecId}(QVariant(document.array().toVariantList()));`);
|
|
1418
|
+
lines.push(` } catch (...) {`);
|
|
1419
|
+
lines.push(` return std::nullopt;`);
|
|
1420
|
+
lines.push(` }`);
|
|
1421
|
+
lines.push(` }`);
|
|
1422
|
+
}
|
|
1423
|
+
if (helper.carriers.includes("object")) {
|
|
1424
|
+
lines.push(` if (transportTag == 'O') {`);
|
|
1425
|
+
lines.push(` QJsonParseError parseError;`);
|
|
1426
|
+
lines.push(` const QJsonDocument document = QJsonDocument::fromJson(payloadBytes, &parseError);`);
|
|
1427
|
+
lines.push(` if (parseError.error != QJsonParseError::NoError || !document.isObject()) {`);
|
|
1428
|
+
lines.push(` return std::nullopt;`);
|
|
1429
|
+
lines.push(` }`);
|
|
1430
|
+
lines.push(` try {`);
|
|
1431
|
+
lines.push(` return decode${helper.codecId}(QVariant(document.object().toVariantMap()));`);
|
|
1432
|
+
lines.push(` } catch (...) {`);
|
|
1433
|
+
lines.push(` return std::nullopt;`);
|
|
1434
|
+
lines.push(` }`);
|
|
1435
|
+
lines.push(` }`);
|
|
1436
|
+
}
|
|
1437
|
+
lines.push(` return std::nullopt;`);
|
|
1438
|
+
lines.push(`}`);
|
|
1439
|
+
lines.push("");
|
|
1440
|
+
}
|
|
865
1441
|
for (const service of spec.services) {
|
|
866
1442
|
for (const member of service.members) {
|
|
867
1443
|
if (member.kind !== "Call" || !member.payloadTypeText)
|
|
@@ -878,14 +1454,6 @@ function renderCppStub(spec, cppTypes) {
|
|
|
878
1454
|
lines.push("");
|
|
879
1455
|
}
|
|
880
1456
|
}
|
|
881
|
-
lines.push(`const ${widgetClassName}::BridgeBindingRow ${widgetClassName}::kBridgeBindings[] = {`);
|
|
882
|
-
for (const service of spec.services) {
|
|
883
|
-
for (const member of service.members) {
|
|
884
|
-
lines.push(` {"${service.name}", "${member.name}", "${member.kind}"},`);
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
lines.push(`};`);
|
|
888
|
-
lines.push("");
|
|
889
1457
|
lines.push(`${widgetClassName}::${widgetClassName}(QWidget* parent) : AnQstWebHostBase(parent), handle(this) {`);
|
|
890
1458
|
lines.push(` static const bool kResourcesInitialized = []() {`);
|
|
891
1459
|
lines.push(` ::qInitResources_${spec.widgetName}();`);
|
|
@@ -912,17 +1480,89 @@ function renderCppStub(spec, cppTypes) {
|
|
|
912
1480
|
for (const member of service.members) {
|
|
913
1481
|
if (member.kind === "DropTarget" && member.payloadTypeText) {
|
|
914
1482
|
const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
|
|
1483
|
+
const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(cppCodecCatalog, service.name, member.name);
|
|
1484
|
+
const typeName = member.payloadTypeText.replace(/\s/g, "");
|
|
915
1485
|
lines.push(` QObject::connect(this, &AnQstWebHostBase::anQstBridge_dropReceived, this, [this](const QString& service, const QString& member, const QVariant& payload, double x, double y) {`);
|
|
916
1486
|
lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
|
|
917
|
-
|
|
1487
|
+
if (payloadSite) {
|
|
1488
|
+
lines.push(` if (payload.type() != QVariant::String) {`);
|
|
1489
|
+
lines.push(` emitHostError(`);
|
|
1490
|
+
lines.push(` QStringLiteral("DeserializationError"),`);
|
|
1491
|
+
lines.push(` QStringLiteral("bridge"),`);
|
|
1492
|
+
lines.push(` QStringLiteral("error"),`);
|
|
1493
|
+
lines.push(` true,`);
|
|
1494
|
+
lines.push(` QStringLiteral("Failed to deserialize DropTarget ${service.name}.${member.name}."),`);
|
|
1495
|
+
lines.push(` {`);
|
|
1496
|
+
lines.push(` {QStringLiteral("service"), QStringLiteral("${service.name}")},`);
|
|
1497
|
+
lines.push(` {QStringLiteral("member"), QStringLiteral("${member.name}")},`);
|
|
1498
|
+
lines.push(` {QStringLiteral("detail"), QStringLiteral("Host did not provide tagged drag/drop payload text.")},`);
|
|
1499
|
+
lines.push(` });`);
|
|
1500
|
+
lines.push(` return;`);
|
|
1501
|
+
lines.push(` }`);
|
|
1502
|
+
lines.push(` const auto decodedPayload = decodeDragDropPayload_${typeName}(payload.toString().toUtf8());`);
|
|
1503
|
+
lines.push(` if (!decodedPayload.has_value()) {`);
|
|
1504
|
+
lines.push(` emitHostError(`);
|
|
1505
|
+
lines.push(` QStringLiteral("DeserializationError"),`);
|
|
1506
|
+
lines.push(` QStringLiteral("bridge"),`);
|
|
1507
|
+
lines.push(` QStringLiteral("error"),`);
|
|
1508
|
+
lines.push(` true,`);
|
|
1509
|
+
lines.push(` QStringLiteral("Failed to deserialize DropTarget ${service.name}.${member.name}."),`);
|
|
1510
|
+
lines.push(` {`);
|
|
1511
|
+
lines.push(` {QStringLiteral("service"), QStringLiteral("${service.name}")},`);
|
|
1512
|
+
lines.push(` {QStringLiteral("member"), QStringLiteral("${member.name}")},`);
|
|
1513
|
+
lines.push(` {QStringLiteral("detail"), QStringLiteral("Tagged drag/drop payload did not match the planned boundary carrier.")},`);
|
|
1514
|
+
lines.push(` });`);
|
|
1515
|
+
lines.push(` return;`);
|
|
1516
|
+
lines.push(` }`);
|
|
1517
|
+
lines.push(` emit ${member.name}(*decodedPayload, x, y);`);
|
|
1518
|
+
}
|
|
1519
|
+
else {
|
|
1520
|
+
lines.push(` emit ${member.name}(payload.value<${cppType}>(), x, y);`);
|
|
1521
|
+
}
|
|
918
1522
|
lines.push(` }`);
|
|
919
1523
|
lines.push(` });`);
|
|
920
1524
|
}
|
|
921
1525
|
else if (member.kind === "HoverTarget" && member.payloadTypeText) {
|
|
922
1526
|
const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
|
|
1527
|
+
const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(cppCodecCatalog, service.name, member.name);
|
|
1528
|
+
const typeName = member.payloadTypeText.replace(/\s/g, "");
|
|
923
1529
|
lines.push(` QObject::connect(this, &AnQstWebHostBase::anQstBridge_hoverUpdated, this, [this](const QString& service, const QString& member, const QVariant& payload, double x, double y) {`);
|
|
924
1530
|
lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
|
|
925
|
-
|
|
1531
|
+
if (payloadSite) {
|
|
1532
|
+
lines.push(` if (payload.type() != QVariant::String) {`);
|
|
1533
|
+
lines.push(` emitHostError(`);
|
|
1534
|
+
lines.push(` QStringLiteral("DeserializationError"),`);
|
|
1535
|
+
lines.push(` QStringLiteral("bridge"),`);
|
|
1536
|
+
lines.push(` QStringLiteral("error"),`);
|
|
1537
|
+
lines.push(` true,`);
|
|
1538
|
+
lines.push(` QStringLiteral("Failed to deserialize HoverTarget ${service.name}.${member.name}."),`);
|
|
1539
|
+
lines.push(` {`);
|
|
1540
|
+
lines.push(` {QStringLiteral("service"), QStringLiteral("${service.name}")},`);
|
|
1541
|
+
lines.push(` {QStringLiteral("member"), QStringLiteral("${member.name}")},`);
|
|
1542
|
+
lines.push(` {QStringLiteral("detail"), QStringLiteral("Host did not provide tagged drag/drop payload text.")},`);
|
|
1543
|
+
lines.push(` });`);
|
|
1544
|
+
lines.push(` return;`);
|
|
1545
|
+
lines.push(` }`);
|
|
1546
|
+
lines.push(` const auto decodedPayload = decodeDragDropPayload_${typeName}(payload.toString().toUtf8());`);
|
|
1547
|
+
lines.push(` if (!decodedPayload.has_value()) {`);
|
|
1548
|
+
lines.push(` emitHostError(`);
|
|
1549
|
+
lines.push(` QStringLiteral("DeserializationError"),`);
|
|
1550
|
+
lines.push(` QStringLiteral("bridge"),`);
|
|
1551
|
+
lines.push(` QStringLiteral("error"),`);
|
|
1552
|
+
lines.push(` true,`);
|
|
1553
|
+
lines.push(` QStringLiteral("Failed to deserialize HoverTarget ${service.name}.${member.name}."),`);
|
|
1554
|
+
lines.push(` {`);
|
|
1555
|
+
lines.push(` {QStringLiteral("service"), QStringLiteral("${service.name}")},`);
|
|
1556
|
+
lines.push(` {QStringLiteral("member"), QStringLiteral("${member.name}")},`);
|
|
1557
|
+
lines.push(` {QStringLiteral("detail"), QStringLiteral("Tagged drag/drop payload did not match the planned boundary carrier.")},`);
|
|
1558
|
+
lines.push(` });`);
|
|
1559
|
+
lines.push(` return;`);
|
|
1560
|
+
lines.push(` }`);
|
|
1561
|
+
lines.push(` emit ${member.name}(*decodedPayload, x, y);`);
|
|
1562
|
+
}
|
|
1563
|
+
else {
|
|
1564
|
+
lines.push(` emit ${member.name}(payload.value<${cppType}>(), x, y);`);
|
|
1565
|
+
}
|
|
926
1566
|
lines.push(` }`);
|
|
927
1567
|
lines.push(` });`);
|
|
928
1568
|
lines.push(` QObject::connect(this, &AnQstWebHostBase::anQstBridge_hoverLeft, this, [this](const QString& service, const QString& member) {`);
|
|
@@ -1028,11 +1668,13 @@ function renderCppStub(spec, cppTypes) {
|
|
|
1028
1668
|
continue;
|
|
1029
1669
|
const timeoutMs = member.timeoutMs;
|
|
1030
1670
|
const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
|
|
1671
|
+
const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(cppCodecCatalog, service.name, member.name);
|
|
1031
1672
|
lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
|
|
1032
1673
|
for (let i = 0; i < member.parameters.length; i++) {
|
|
1033
1674
|
const p = member.parameters[i];
|
|
1034
1675
|
const pType = cppTypes.mapTypeText(p.typeText, [service.name, member.name, p.name]);
|
|
1035
|
-
|
|
1676
|
+
const paramSite = (0, boundary_codecs_1.getBoundaryParameterSite)(cppCodecCatalog, service.name, member.name, p.name);
|
|
1677
|
+
lines.push(` const ${pType} ${p.name} = ${paramSite ? `decode${paramSite.codecId}(args.value(${i}))` : variantToCppExpression(pType, `args.value(${i})`)};`);
|
|
1036
1678
|
}
|
|
1037
1679
|
lines.push(` const QString requestId = QStringLiteral("call-%1").arg(++m_callRequestCounter);`);
|
|
1038
1680
|
lines.push(` const QString queueKey = makeBindingKey(QStringLiteral("${service.name}"), QStringLiteral("${member.name}"));`);
|
|
@@ -1049,7 +1691,7 @@ function renderCppStub(spec, cppTypes) {
|
|
|
1049
1691
|
lines.push(` try {`);
|
|
1050
1692
|
const callArgs = member.parameters.map((p) => p.name).join(", ");
|
|
1051
1693
|
lines.push(` const ${cppType} result = m_${member.name}Handler(${callArgs});`);
|
|
1052
|
-
lines.push(` return ${cppToVariantExpression(cppType, "result")};`);
|
|
1694
|
+
lines.push(` return ${payloadSite ? `encode${payloadSite.codecId}(result)` : cppToVariantExpression(cppType, "result")};`);
|
|
1053
1695
|
lines.push(` } catch (const std::exception& ex) {`);
|
|
1054
1696
|
lines.push(` return QVariantMap{`);
|
|
1055
1697
|
lines.push(` {QStringLiteral("code"), QStringLiteral("CallHandlerError")},`);
|
|
@@ -1101,7 +1743,8 @@ function renderCppStub(spec, cppTypes) {
|
|
|
1101
1743
|
for (let i = 0; i < member.parameters.length; i++) {
|
|
1102
1744
|
const p = member.parameters[i];
|
|
1103
1745
|
const pType = cppTypes.mapTypeText(p.typeText, [service.name, member.name, p.name]);
|
|
1104
|
-
|
|
1746
|
+
const paramSite = (0, boundary_codecs_1.getBoundaryParameterSite)(cppCodecCatalog, service.name, member.name, p.name);
|
|
1747
|
+
lines.push(` const ${pType} ${p.name} = ${paramSite ? `decode${paramSite.codecId}(args.value(${i}))` : variantToCppExpression(pType, `args.value(${i})`)};`);
|
|
1105
1748
|
}
|
|
1106
1749
|
const argNames = member.parameters.map((p) => p.name).join(", ");
|
|
1107
1750
|
lines.push(` emit ${member.name}(${argNames});`);
|
|
@@ -1117,8 +1760,9 @@ function renderCppStub(spec, cppTypes) {
|
|
|
1117
1760
|
if (member.kind !== "Input" || !member.payloadTypeText)
|
|
1118
1761
|
continue;
|
|
1119
1762
|
const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
|
|
1763
|
+
const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(cppCodecCatalog, service.name, member.name);
|
|
1120
1764
|
lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
|
|
1121
|
-
lines.push(` const ${cppType} typedValue = ${variantToCppExpression(cppType, "value")};`);
|
|
1765
|
+
lines.push(` const ${cppType} typedValue = ${payloadSite ? `decode${payloadSite.codecId}(value)` : variantToCppExpression(cppType, "value")};`);
|
|
1122
1766
|
lines.push(` set${pascalCase(member.name)}(typedValue);`);
|
|
1123
1767
|
lines.push(` if (m_${member.name}Handler) m_${member.name}Handler(typedValue);`);
|
|
1124
1768
|
lines.push(` return;`);
|
|
@@ -1138,12 +1782,14 @@ function renderCppStub(spec, cppTypes) {
|
|
|
1138
1782
|
}
|
|
1139
1783
|
if (member.kind === "Slot") {
|
|
1140
1784
|
const ret = member.payloadTypeText ? cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]) : "void";
|
|
1785
|
+
const payloadSite = member.payloadTypeText ? (0, boundary_codecs_1.getBoundaryPayloadSite)(cppCodecCatalog, service.name, member.name) : undefined;
|
|
1141
1786
|
const args = member.parameters.map((p) => `${cppTypes.mapTypeText(p.typeText, [service.name, member.name, p.name])} ${p.name}`).join(", ");
|
|
1142
1787
|
lines.push(`${ret} ${widgetClassName}::slot_${member.name}(${args}) {`);
|
|
1143
1788
|
lines.push(` QVariantList invokeArgs;`);
|
|
1144
1789
|
for (const p of member.parameters) {
|
|
1145
1790
|
const pType = mapTsTypeToCpp(p.typeText);
|
|
1146
|
-
|
|
1791
|
+
const paramSite = (0, boundary_codecs_1.getBoundaryParameterSite)(cppCodecCatalog, service.name, member.name, p.name);
|
|
1792
|
+
lines.push(` invokeArgs.push_back(${paramSite ? `encode${paramSite.codecId}(${p.name})` : cppToVariantExpression(pType, p.name)});`);
|
|
1147
1793
|
}
|
|
1148
1794
|
lines.push(` QVariant result;`);
|
|
1149
1795
|
lines.push(` QString invokeError;`);
|
|
@@ -1160,13 +1806,14 @@ function renderCppStub(spec, cppTypes) {
|
|
|
1160
1806
|
lines.push(` return;`);
|
|
1161
1807
|
}
|
|
1162
1808
|
else {
|
|
1163
|
-
lines.push(` return ${variantToCppExpression(ret, "result")};`);
|
|
1809
|
+
lines.push(` return ${payloadSite ? `decode${payloadSite.codecId}(result)` : variantToCppExpression(ret, "result")};`);
|
|
1164
1810
|
}
|
|
1165
1811
|
lines.push("}");
|
|
1166
1812
|
lines.push("");
|
|
1167
1813
|
}
|
|
1168
1814
|
else if ((member.kind === "Input" || member.kind === "Output") && member.payloadTypeText) {
|
|
1169
1815
|
const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
|
|
1816
|
+
const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(cppCodecCatalog, service.name, member.name);
|
|
1170
1817
|
const cap = member.name.charAt(0).toUpperCase() + member.name.slice(1);
|
|
1171
1818
|
lines.push(`${cppType} ${widgetClassName}::${member.name}() const {`);
|
|
1172
1819
|
lines.push(` return m_${member.name};`);
|
|
@@ -1174,9 +1821,40 @@ function renderCppStub(spec, cppTypes) {
|
|
|
1174
1821
|
lines.push("");
|
|
1175
1822
|
lines.push(`void ${widgetClassName}::set${cap}(const ${cppType}& value) {`);
|
|
1176
1823
|
lines.push(` if (m_${member.name} == value) return;`);
|
|
1824
|
+
if (member.kind === "Output") {
|
|
1825
|
+
lines.push(` QVariant encodedValue;`);
|
|
1826
|
+
lines.push(` try {`);
|
|
1827
|
+
lines.push(` encodedValue = ${payloadSite ? `encode${payloadSite.codecId}(value)` : cppToVariantExpression(cppType, "value")};`);
|
|
1828
|
+
lines.push(` } catch (const std::exception& ex) {`);
|
|
1829
|
+
lines.push(` emitHostError(`);
|
|
1830
|
+
lines.push(` QStringLiteral("SerializationError"),`);
|
|
1831
|
+
lines.push(` QStringLiteral("bridge"),`);
|
|
1832
|
+
lines.push(` QStringLiteral("error"),`);
|
|
1833
|
+
lines.push(` true,`);
|
|
1834
|
+
lines.push(` QStringLiteral("Failed to serialize Output ${service.name}.${member.name}."),`);
|
|
1835
|
+
lines.push(` {`);
|
|
1836
|
+
lines.push(` {QStringLiteral("service"), QStringLiteral("${service.name}")},`);
|
|
1837
|
+
lines.push(` {QStringLiteral("member"), QStringLiteral("${member.name}")},`);
|
|
1838
|
+
lines.push(` {QStringLiteral("detail"), QString::fromUtf8(ex.what())},`);
|
|
1839
|
+
lines.push(` });`);
|
|
1840
|
+
lines.push(` return;`);
|
|
1841
|
+
lines.push(` } catch (...) {`);
|
|
1842
|
+
lines.push(` emitHostError(`);
|
|
1843
|
+
lines.push(` QStringLiteral("SerializationError"),`);
|
|
1844
|
+
lines.push(` QStringLiteral("bridge"),`);
|
|
1845
|
+
lines.push(` QStringLiteral("error"),`);
|
|
1846
|
+
lines.push(` true,`);
|
|
1847
|
+
lines.push(` QStringLiteral("Failed to serialize Output ${service.name}.${member.name}."),`);
|
|
1848
|
+
lines.push(` {`);
|
|
1849
|
+
lines.push(` {QStringLiteral("service"), QStringLiteral("${service.name}")},`);
|
|
1850
|
+
lines.push(` {QStringLiteral("member"), QStringLiteral("${member.name}")},`);
|
|
1851
|
+
lines.push(` });`);
|
|
1852
|
+
lines.push(` return;`);
|
|
1853
|
+
lines.push(` }`);
|
|
1854
|
+
}
|
|
1177
1855
|
lines.push(` m_${member.name} = value;`);
|
|
1178
1856
|
if (member.kind === "Output") {
|
|
1179
|
-
lines.push(` setOutputValue(QStringLiteral("${service.name}"), QStringLiteral("${member.name}"),
|
|
1857
|
+
lines.push(` setOutputValue(QStringLiteral("${service.name}"), QStringLiteral("${member.name}"), encodedValue);`);
|
|
1180
1858
|
}
|
|
1181
1859
|
lines.push(` emit ${member.name}Changed(value);`);
|
|
1182
1860
|
lines.push("}");
|
|
@@ -1363,59 +2041,144 @@ function slotHandlerReturnType(tsRet) {
|
|
|
1363
2041
|
}
|
|
1364
2042
|
return `${tsRet} | Promise<${tsRet}> | Error`;
|
|
1365
2043
|
}
|
|
1366
|
-
function renderTsService(spec, serviceName) {
|
|
2044
|
+
function renderTsService(spec, serviceName, codecCatalog) {
|
|
1367
2045
|
const members = spec.services.find((s) => s.name === serviceName)?.members ?? [];
|
|
1368
2046
|
const fieldLines = [];
|
|
1369
2047
|
const methodLines = [];
|
|
1370
2048
|
const setMembers = [];
|
|
1371
2049
|
const onSlotMembers = [];
|
|
1372
2050
|
const constructorBodyLines = [];
|
|
1373
|
-
constructorBodyLines.push(" this._bridge.ready().catch((error) => console.error('AnQst bridge ready() failed', error, (error as { stack?: unknown })?.stack));");
|
|
1374
2051
|
for (const m of members) {
|
|
1375
2052
|
const args = m.parameters.map((p) => `${p.name}: ${mapTypeTextToTs(p.typeText)}`).join(", ");
|
|
1376
|
-
const
|
|
1377
|
-
const
|
|
2053
|
+
const paramSites = m.parameters.map((p) => (0, boundary_codecs_1.getBoundaryParameterSite)(codecCatalog, serviceName, m.name, p.name));
|
|
2054
|
+
const encodedValueArray = paramSites.length > 0
|
|
2055
|
+
? `[${m.parameters.map((p, index) => `${paramSites[index] ? `encode${paramSites[index].codecId}(${p.name})` : p.name}`).join(", ")}]`
|
|
2056
|
+
: "[]";
|
|
2057
|
+
const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(codecCatalog, serviceName, m.name);
|
|
1378
2058
|
if (m.kind === "Call") {
|
|
1379
2059
|
const ret = mapTypeTextToTs(m.payloadTypeText ?? "void");
|
|
1380
|
-
|
|
2060
|
+
if (payloadSite) {
|
|
2061
|
+
methodLines.push(` async ${m.name}(${args}): Promise<${ret}> { const result = await this._bridge.call<unknown>("${serviceName}", "${m.name}", ${encodedValueArray}); return decode${payloadSite.codecId}(result); }`);
|
|
2062
|
+
}
|
|
2063
|
+
else {
|
|
2064
|
+
methodLines.push(` async ${m.name}(${args}): Promise<${ret}> { return this._bridge.call<${ret}>("${serviceName}", "${m.name}", ${encodedValueArray}); }`);
|
|
2065
|
+
}
|
|
1381
2066
|
continue;
|
|
1382
2067
|
}
|
|
1383
2068
|
if (m.kind === "Emitter") {
|
|
1384
|
-
methodLines.push(` ${m.name}(${args}): void {
|
|
2069
|
+
methodLines.push(` ${m.name}(${args}): void {`);
|
|
2070
|
+
methodLines.push(` let encodedArgs: unknown[];`);
|
|
2071
|
+
methodLines.push(` try {`);
|
|
2072
|
+
methodLines.push(` encodedArgs = ${encodedValueArray};`);
|
|
2073
|
+
methodLines.push(` } catch (error) {`);
|
|
2074
|
+
methodLines.push(` this._bridge.reportFrontendDiagnostic({`);
|
|
2075
|
+
methodLines.push(` code: "SerializationError",`);
|
|
2076
|
+
methodLines.push(` severity: "error",`);
|
|
2077
|
+
methodLines.push(` category: "bridge",`);
|
|
2078
|
+
methodLines.push(` recoverable: true,`);
|
|
2079
|
+
methodLines.push(` message: \`Failed to serialize Emitter ${serviceName}.${m.name}: \${errorMessage(error)}\`,`);
|
|
2080
|
+
methodLines.push(` service: "${serviceName}",`);
|
|
2081
|
+
methodLines.push(` member: "${m.name}",`);
|
|
2082
|
+
methodLines.push(` context: { interaction: "Emitter" }`);
|
|
2083
|
+
methodLines.push(` });`);
|
|
2084
|
+
methodLines.push(` return;`);
|
|
2085
|
+
methodLines.push(` }`);
|
|
2086
|
+
methodLines.push(` this._bridge.emit("${serviceName}", "${m.name}", encodedArgs);`);
|
|
2087
|
+
methodLines.push(` }`);
|
|
1385
2088
|
continue;
|
|
1386
2089
|
}
|
|
1387
2090
|
if (m.kind === "Slot") {
|
|
1388
2091
|
const ret = mapTypeTextToTs(m.payloadTypeText ?? "void");
|
|
2092
|
+
const decodedArgs = m.parameters.map((p, index) => `${paramSites[index] ? `decode${paramSites[index].codecId}(wireArgs[${index}])` : `wireArgs[${index}] as ${mapTypeTextToTs(p.typeText)}`}`).join(", ");
|
|
1389
2093
|
onSlotMembers.push(` ${m.name}: (handler: (${args}) => ${slotHandlerReturnType(ret)}): void => {`);
|
|
1390
|
-
onSlotMembers.push(` this._bridge.registerSlot("${serviceName}", "${m.name}",
|
|
2094
|
+
onSlotMembers.push(` this._bridge.registerSlot("${serviceName}", "${m.name}", (...wireArgs: unknown[]) => {`);
|
|
2095
|
+
onSlotMembers.push(` const result = handler(${decodedArgs});`);
|
|
2096
|
+
if (payloadSite) {
|
|
2097
|
+
onSlotMembers.push(` if (result instanceof Promise) return result.then((value) => value instanceof Error ? value : encode${payloadSite.codecId}(value));`);
|
|
2098
|
+
onSlotMembers.push(` return result instanceof Error ? result : encode${payloadSite.codecId}(result);`);
|
|
2099
|
+
}
|
|
2100
|
+
else {
|
|
2101
|
+
onSlotMembers.push(" return result;");
|
|
2102
|
+
}
|
|
2103
|
+
onSlotMembers.push(" });");
|
|
1391
2104
|
onSlotMembers.push(" },");
|
|
1392
2105
|
continue;
|
|
1393
2106
|
}
|
|
1394
2107
|
if ((m.kind === "Input" || m.kind === "Output") && m.payloadTypeText) {
|
|
1395
2108
|
const tsType = mapTypeTextToTs(m.payloadTypeText);
|
|
1396
|
-
fieldLines.push(` private readonly _${m.name} = signal<${tsType}>(
|
|
1397
|
-
methodLines.push(` ${m.name}(): ${tsType} { return this._${m.name}(); }`);
|
|
2109
|
+
fieldLines.push(` private readonly _${m.name} = signal<${tsType} | undefined>(undefined);`);
|
|
2110
|
+
methodLines.push(` ${m.name}(): ${tsType} | undefined { return this._${m.name}(); }`);
|
|
1398
2111
|
if (m.kind === "Input") {
|
|
1399
2112
|
setMembers.push(` ${m.name}: (value: ${tsType}): void => {`);
|
|
2113
|
+
setMembers.push(` let encodedValue: unknown;`);
|
|
2114
|
+
setMembers.push(` try {`);
|
|
2115
|
+
setMembers.push(` encodedValue = ${payloadSite ? `encode${payloadSite.codecId}(value)` : "value"};`);
|
|
2116
|
+
setMembers.push(` } catch (error) {`);
|
|
2117
|
+
setMembers.push(` this._bridge.reportFrontendDiagnostic({`);
|
|
2118
|
+
setMembers.push(` code: "SerializationError",`);
|
|
2119
|
+
setMembers.push(` severity: "error",`);
|
|
2120
|
+
setMembers.push(` category: "bridge",`);
|
|
2121
|
+
setMembers.push(` recoverable: true,`);
|
|
2122
|
+
setMembers.push(` message: \`Failed to serialize Input ${serviceName}.${m.name}: \${errorMessage(error)}\`,`);
|
|
2123
|
+
setMembers.push(` service: "${serviceName}",`);
|
|
2124
|
+
setMembers.push(` member: "${m.name}",`);
|
|
2125
|
+
setMembers.push(` context: { interaction: "Input" }`);
|
|
2126
|
+
setMembers.push(` });`);
|
|
2127
|
+
setMembers.push(` return;`);
|
|
2128
|
+
setMembers.push(` }`);
|
|
1400
2129
|
setMembers.push(` this._${m.name}.set(value);`);
|
|
1401
|
-
setMembers.push(` this._bridge.setInput("${serviceName}", "${m.name}",
|
|
2130
|
+
setMembers.push(` this._bridge.setInput("${serviceName}", "${m.name}", encodedValue);`);
|
|
1402
2131
|
setMembers.push(" },");
|
|
1403
2132
|
}
|
|
1404
2133
|
if (m.kind === "Output") {
|
|
1405
|
-
constructorBodyLines.push(` this._bridge.onOutput("${serviceName}", "${m.name}", (value) =>
|
|
2134
|
+
constructorBodyLines.push(` this._bridge.onOutput("${serviceName}", "${m.name}", (value) => {`);
|
|
2135
|
+
constructorBodyLines.push(` this._${m.name}.set(${payloadSite ? `decode${payloadSite.codecId}(value)` : `value as ${tsType}`});`);
|
|
2136
|
+
constructorBodyLines.push(` });`);
|
|
1406
2137
|
}
|
|
1407
2138
|
}
|
|
1408
2139
|
if (m.kind === "DropTarget" && m.payloadTypeText) {
|
|
1409
2140
|
const tsType = mapTypeTextToTs(m.payloadTypeText);
|
|
2141
|
+
const typeName = m.payloadTypeText.replace(/\s/g, "");
|
|
1410
2142
|
fieldLines.push(` private readonly _${m.name} = signal<{ payload: ${tsType}; x: number; y: number } | null>(null);`);
|
|
1411
2143
|
methodLines.push(` ${m.name}(): { payload: ${tsType}; x: number; y: number } | null { return this._${m.name}(); }`);
|
|
1412
|
-
constructorBodyLines.push(` this._bridge.onDrop("${serviceName}", "${m.name}", (payload, x, y) =>
|
|
2144
|
+
constructorBodyLines.push(` this._bridge.onDrop("${serviceName}", "${m.name}", (payload, x, y) => {`);
|
|
2145
|
+
constructorBodyLines.push(` try {`);
|
|
2146
|
+
constructorBodyLines.push(` this._${m.name}.set({ payload: ${payloadSite ? `decodeDragDropPayload_${typeName}(payload)` : `payload as ${tsType}`}, x, y });`);
|
|
2147
|
+
constructorBodyLines.push(` } catch (error) {`);
|
|
2148
|
+
constructorBodyLines.push(` this._bridge.reportFrontendDiagnostic({`);
|
|
2149
|
+
constructorBodyLines.push(` code: "DeserializationError",`);
|
|
2150
|
+
constructorBodyLines.push(` severity: "error",`);
|
|
2151
|
+
constructorBodyLines.push(` category: "bridge",`);
|
|
2152
|
+
constructorBodyLines.push(` recoverable: true,`);
|
|
2153
|
+
constructorBodyLines.push(` message: \`Failed to deserialize DropTarget ${serviceName}.${m.name}: \${errorMessage(error)}\`,`);
|
|
2154
|
+
constructorBodyLines.push(` service: "${serviceName}",`);
|
|
2155
|
+
constructorBodyLines.push(` member: "${m.name}",`);
|
|
2156
|
+
constructorBodyLines.push(` context: { interaction: "DropTarget" }`);
|
|
2157
|
+
constructorBodyLines.push(` });`);
|
|
2158
|
+
constructorBodyLines.push(` }`);
|
|
2159
|
+
constructorBodyLines.push(` });`);
|
|
1413
2160
|
}
|
|
1414
2161
|
if (m.kind === "HoverTarget" && m.payloadTypeText) {
|
|
1415
2162
|
const tsType = mapTypeTextToTs(m.payloadTypeText);
|
|
2163
|
+
const typeName = m.payloadTypeText.replace(/\s/g, "");
|
|
1416
2164
|
fieldLines.push(` private readonly _${m.name} = signal<{ payload: ${tsType}; x: number; y: number } | null>(null);`);
|
|
1417
2165
|
methodLines.push(` ${m.name}(): { payload: ${tsType}; x: number; y: number } | null { return this._${m.name}(); }`);
|
|
1418
|
-
constructorBodyLines.push(` this._bridge.onHover("${serviceName}", "${m.name}", (payload, x, y) =>
|
|
2166
|
+
constructorBodyLines.push(` this._bridge.onHover("${serviceName}", "${m.name}", (payload, x, y) => {`);
|
|
2167
|
+
constructorBodyLines.push(` try {`);
|
|
2168
|
+
constructorBodyLines.push(` this._${m.name}.set({ payload: ${payloadSite ? `decodeDragDropPayload_${typeName}(payload)` : `payload as ${tsType}`}, x, y });`);
|
|
2169
|
+
constructorBodyLines.push(` } catch (error) {`);
|
|
2170
|
+
constructorBodyLines.push(` this._bridge.reportFrontendDiagnostic({`);
|
|
2171
|
+
constructorBodyLines.push(` code: "DeserializationError",`);
|
|
2172
|
+
constructorBodyLines.push(` severity: "error",`);
|
|
2173
|
+
constructorBodyLines.push(` category: "bridge",`);
|
|
2174
|
+
constructorBodyLines.push(` recoverable: true,`);
|
|
2175
|
+
constructorBodyLines.push(` message: \`Failed to deserialize HoverTarget ${serviceName}.${m.name}: \${errorMessage(error)}\`,`);
|
|
2176
|
+
constructorBodyLines.push(` service: "${serviceName}",`);
|
|
2177
|
+
constructorBodyLines.push(` member: "${m.name}",`);
|
|
2178
|
+
constructorBodyLines.push(` context: { interaction: "HoverTarget" }`);
|
|
2179
|
+
constructorBodyLines.push(` });`);
|
|
2180
|
+
constructorBodyLines.push(` }`);
|
|
2181
|
+
constructorBodyLines.push(` });`);
|
|
1419
2182
|
constructorBodyLines.push(` this._bridge.onHoverLeft("${serviceName}", "${m.name}", () => this._${m.name}.set(null));`);
|
|
1420
2183
|
}
|
|
1421
2184
|
}
|
|
@@ -1464,7 +2227,7 @@ function renderTsServiceDts(spec, serviceName) {
|
|
|
1464
2227
|
}
|
|
1465
2228
|
if ((m.kind === "Input" || m.kind === "Output") && m.payloadTypeText) {
|
|
1466
2229
|
const tsType = mapTypeTextToTs(m.payloadTypeText);
|
|
1467
|
-
classMembers.push(` ${m.name}(): ${tsType};`);
|
|
2230
|
+
classMembers.push(` ${m.name}(): ${tsType} | undefined;`);
|
|
1468
2231
|
if (m.kind === "Input") {
|
|
1469
2232
|
setMembers.push(` ${m.name}(value: ${tsType}): void;`);
|
|
1470
2233
|
}
|
|
@@ -1494,15 +2257,21 @@ export declare class ${serviceName} {
|
|
|
1494
2257
|
readonly onSlot: ${onSlotInterfaceName};${classMemberBlock}
|
|
1495
2258
|
}`;
|
|
1496
2259
|
}
|
|
1497
|
-
function renderTsServices(spec) {
|
|
1498
|
-
const serviceClasses = spec.services.map((s) => renderTsService(spec, s.name)).join("\n");
|
|
2260
|
+
function renderTsServices(spec, codecCatalog) {
|
|
2261
|
+
const serviceClasses = spec.services.map((s) => renderTsService(spec, s.name, codecCatalog)).join("\n");
|
|
1499
2262
|
const externalTypeImports = renderRequiredTypeImports(spec, `frontend/${(0, layout_1.generatedFrontendDirName)(spec.widgetName)}/services.ts`).trim();
|
|
1500
2263
|
const localTypeImports = renderLocalTypeImports(spec).trim();
|
|
1501
2264
|
const typeImports = [externalTypeImports, localTypeImports].filter((s) => s.length > 0).join("\n");
|
|
1502
2265
|
const typeImportsBlock = typeImports.length > 0 ? `${typeImports}\n\n` : "";
|
|
2266
|
+
const dragDropHelperBlock = renderTsDragDropPayloadHelpers(spec, codecCatalog).trim();
|
|
2267
|
+
const dragDropHelpers = dragDropHelperBlock.length > 0 ? `\n// Drag/drop payload helpers\n${dragDropHelperBlock}\n` : "";
|
|
1503
2268
|
return `import { Injectable, inject, signal } from "@angular/core";
|
|
1504
2269
|
${typeImportsBlock}
|
|
1505
2270
|
|
|
2271
|
+
// Boundary codec plan helpers
|
|
2272
|
+
${(0, boundary_codecs_1.renderTsBoundaryCodecHelpers)(codecCatalog)}
|
|
2273
|
+
${dragDropHelpers}
|
|
2274
|
+
|
|
1506
2275
|
type SlotHandler = (...args: unknown[]) => unknown;
|
|
1507
2276
|
type OutputHandler = (value: unknown) => void;
|
|
1508
2277
|
type SlotInvocationListener = (requestId: string, service: string, member: string, args: unknown[]) => void;
|
|
@@ -1510,6 +2279,28 @@ type OutputListener = (service: string, member: string, value: unknown) => void;
|
|
|
1510
2279
|
type DropListener = (service: string, member: string, payload: unknown, x: number, y: number) => void;
|
|
1511
2280
|
type HoverListener = (service: string, member: string, payload: unknown, x: number, y: number) => void;
|
|
1512
2281
|
type HoverLeftListener = (service: string, member: string) => void;
|
|
2282
|
+
type HostDiagnosticListener = (payload: unknown) => void;
|
|
2283
|
+
type DisconnectListener = () => void;
|
|
2284
|
+
|
|
2285
|
+
export type AnQstBridgeSeverity = "info" | "warn" | "error" | "fatal";
|
|
2286
|
+
export type AnQstBridgeSource = "frontend" | "host";
|
|
2287
|
+
export type AnQstBridgeTransport = "qt-webchannel" | "dev-websocket";
|
|
2288
|
+
export type AnQstBridgeState = "starting" | "ready" | "failed" | "disconnected";
|
|
2289
|
+
|
|
2290
|
+
export interface AnQstBridgeDiagnostic {
|
|
2291
|
+
code: string;
|
|
2292
|
+
severity: AnQstBridgeSeverity;
|
|
2293
|
+
category: string;
|
|
2294
|
+
recoverable: boolean;
|
|
2295
|
+
message: string;
|
|
2296
|
+
timestamp: string;
|
|
2297
|
+
source: AnQstBridgeSource;
|
|
2298
|
+
transport?: AnQstBridgeTransport;
|
|
2299
|
+
service?: string;
|
|
2300
|
+
member?: string;
|
|
2301
|
+
requestId?: string;
|
|
2302
|
+
context?: Record<string, unknown>;
|
|
2303
|
+
}
|
|
1513
2304
|
|
|
1514
2305
|
interface HostBridgeApi {
|
|
1515
2306
|
anQstBridge_call(service: string, member: string, args: unknown[], callback: (result: unknown) => void): void;
|
|
@@ -1521,6 +2312,7 @@ interface HostBridgeApi {
|
|
|
1521
2312
|
anQstBridge_slotInvocationRequested: {
|
|
1522
2313
|
connect: (cb: (requestId: string, service: string, member: string, args: unknown[]) => void) => void;
|
|
1523
2314
|
};
|
|
2315
|
+
anQstBridge_hostDiagnostic?: { connect: (cb: (payload: unknown) => void) => void };
|
|
1524
2316
|
anQstBridge_dropReceived: { connect: (cb: (service: string, member: string, payload: unknown, x: number, y: number) => void) => void };
|
|
1525
2317
|
anQstBridge_hoverUpdated: { connect: (cb: (service: string, member: string, payload: unknown, x: number, y: number) => void) => void };
|
|
1526
2318
|
anQstBridge_hoverLeft: { connect: (cb: (service: string, member: string) => void) => void };
|
|
@@ -1534,6 +2326,7 @@ interface QWebChannelCtor {
|
|
|
1534
2326
|
}
|
|
1535
2327
|
|
|
1536
2328
|
interface BridgeAdapter {
|
|
2329
|
+
readonly transport: AnQstBridgeTransport;
|
|
1537
2330
|
call<T>(service: string, member: string, args: unknown[]): Promise<T>;
|
|
1538
2331
|
emit(service: string, member: string, args: unknown[]): void;
|
|
1539
2332
|
setInput(service: string, member: string, value: unknown): void;
|
|
@@ -1541,11 +2334,82 @@ interface BridgeAdapter {
|
|
|
1541
2334
|
resolveSlot(requestId: string, ok: boolean, payload: unknown, error: string): void;
|
|
1542
2335
|
onOutput(handler: OutputListener): void;
|
|
1543
2336
|
onSlotInvocation(handler: SlotInvocationListener): void;
|
|
2337
|
+
onHostDiagnostic(handler: HostDiagnosticListener): void;
|
|
2338
|
+
onDisconnected(handler: DisconnectListener): void;
|
|
1544
2339
|
onDrop(handler: DropListener): void;
|
|
1545
2340
|
onHover(handler: HoverListener): void;
|
|
1546
2341
|
onHoverLeft(handler: HoverLeftListener): void;
|
|
1547
2342
|
}
|
|
1548
2343
|
|
|
2344
|
+
function errorMessage(error: unknown): string {
|
|
2345
|
+
if (error instanceof Error && typeof error.message === "string" && error.message.length > 0) {
|
|
2346
|
+
return error.message;
|
|
2347
|
+
}
|
|
2348
|
+
return String(error);
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
function normalizeSeverity(value: unknown): AnQstBridgeSeverity {
|
|
2352
|
+
if (value === "info" || value === "warn" || value === "error" || value === "fatal") {
|
|
2353
|
+
return value;
|
|
2354
|
+
}
|
|
2355
|
+
return "error";
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
2359
|
+
if (value === null || typeof value !== "object") {
|
|
2360
|
+
return undefined;
|
|
2361
|
+
}
|
|
2362
|
+
return value as Record<string, unknown>;
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
function readString(record: Record<string, unknown> | undefined, key: string): string | undefined {
|
|
2366
|
+
const value = record?.[key];
|
|
2367
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
function readBoolean(record: Record<string, unknown> | undefined, key: string): boolean | undefined {
|
|
2371
|
+
const value = record?.[key];
|
|
2372
|
+
return typeof value === "boolean" ? value : undefined;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
function readContext(record: Record<string, unknown> | undefined): Record<string, unknown> | undefined {
|
|
2376
|
+
const context = asRecord(record?.["context"]);
|
|
2377
|
+
return context === undefined ? undefined : context;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
function normalizeHostDiagnostic(
|
|
2381
|
+
payload: unknown,
|
|
2382
|
+
transport: AnQstBridgeTransport
|
|
2383
|
+
): Omit<AnQstBridgeDiagnostic, "timestamp"> {
|
|
2384
|
+
const row = asRecord(payload);
|
|
2385
|
+
if (row === undefined) {
|
|
2386
|
+
return {
|
|
2387
|
+
code: "HostDiagnosticMalformed",
|
|
2388
|
+
severity: "error",
|
|
2389
|
+
category: "bridge",
|
|
2390
|
+
recoverable: true,
|
|
2391
|
+
message: "Host emitted a malformed diagnostic payload.",
|
|
2392
|
+
source: "host",
|
|
2393
|
+
transport
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
const context = readContext(row);
|
|
2398
|
+
return {
|
|
2399
|
+
code: readString(row, "code") ?? "HostDiagnostic",
|
|
2400
|
+
severity: normalizeSeverity(row["severity"]),
|
|
2401
|
+
category: readString(row, "category") ?? "bridge",
|
|
2402
|
+
recoverable: readBoolean(row, "recoverable") ?? true,
|
|
2403
|
+
message: readString(row, "message") ?? "Host emitted a diagnostic payload.",
|
|
2404
|
+
source: "host",
|
|
2405
|
+
transport,
|
|
2406
|
+
service: readString(row, "service") ?? readString(context, "service"),
|
|
2407
|
+
member: readString(row, "member") ?? readString(context, "member"),
|
|
2408
|
+
requestId: readString(row, "requestId") ?? readString(context, "requestId"),
|
|
2409
|
+
context
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
|
|
1549
2413
|
function isBridgeCallError(value: unknown): value is {
|
|
1550
2414
|
code: unknown;
|
|
1551
2415
|
message: unknown;
|
|
@@ -1565,6 +2429,8 @@ function isBridgeCallError(value: unknown): value is {
|
|
|
1565
2429
|
}
|
|
1566
2430
|
|
|
1567
2431
|
class QtWebChannelAdapter implements BridgeAdapter {
|
|
2432
|
+
readonly transport = "qt-webchannel" as const;
|
|
2433
|
+
|
|
1568
2434
|
private constructor(private readonly host: HostBridgeApi) {}
|
|
1569
2435
|
|
|
1570
2436
|
static async create(): Promise<QtWebChannelAdapter> {
|
|
@@ -1632,6 +2498,14 @@ class QtWebChannelAdapter implements BridgeAdapter {
|
|
|
1632
2498
|
this.host.anQstBridge_slotInvocationRequested.connect(handler);
|
|
1633
2499
|
}
|
|
1634
2500
|
|
|
2501
|
+
onHostDiagnostic(handler: HostDiagnosticListener): void {
|
|
2502
|
+
this.host.anQstBridge_hostDiagnostic?.connect(handler);
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
onDisconnected(_handler: DisconnectListener): void {
|
|
2506
|
+
// QWebChannel does not expose a deterministic disconnect event here.
|
|
2507
|
+
}
|
|
2508
|
+
|
|
1635
2509
|
onDrop(handler: DropListener): void {
|
|
1636
2510
|
this.host.anQstBridge_dropReceived.connect(handler);
|
|
1637
2511
|
}
|
|
@@ -1646,6 +2520,7 @@ class QtWebChannelAdapter implements BridgeAdapter {
|
|
|
1646
2520
|
}
|
|
1647
2521
|
|
|
1648
2522
|
class WebSocketBridgeAdapter implements BridgeAdapter {
|
|
2523
|
+
readonly transport = "dev-websocket" as const;
|
|
1649
2524
|
private readonly pending = new Map<string, {
|
|
1650
2525
|
service: string;
|
|
1651
2526
|
member: string;
|
|
@@ -1655,6 +2530,8 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
|
|
|
1655
2530
|
}>();
|
|
1656
2531
|
private readonly outputListeners: OutputListener[] = [];
|
|
1657
2532
|
private readonly slotListeners: SlotInvocationListener[] = [];
|
|
2533
|
+
private readonly hostDiagnosticListeners: HostDiagnosticListener[] = [];
|
|
2534
|
+
private readonly disconnectListeners: DisconnectListener[] = [];
|
|
1658
2535
|
private readonly dropListeners: DropListener[] = [];
|
|
1659
2536
|
private readonly hoverListeners: HoverListener[] = [];
|
|
1660
2537
|
private readonly hoverLeftListeners: HoverLeftListener[] = [];
|
|
@@ -1726,7 +2603,9 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
|
|
|
1726
2603
|
return;
|
|
1727
2604
|
}
|
|
1728
2605
|
if (type === "hostError") {
|
|
1729
|
-
|
|
2606
|
+
for (const listener of this.hostDiagnosticListeners) {
|
|
2607
|
+
listener(message["payload"]);
|
|
2608
|
+
}
|
|
1730
2609
|
return;
|
|
1731
2610
|
}
|
|
1732
2611
|
if (type === "widgetReattached") {
|
|
@@ -1745,6 +2624,9 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
|
|
|
1745
2624
|
});
|
|
1746
2625
|
}
|
|
1747
2626
|
this.pending.clear();
|
|
2627
|
+
for (const listener of this.disconnectListeners) {
|
|
2628
|
+
listener();
|
|
2629
|
+
}
|
|
1748
2630
|
});
|
|
1749
2631
|
}
|
|
1750
2632
|
|
|
@@ -1813,6 +2695,14 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
|
|
|
1813
2695
|
this.slotListeners.push(handler);
|
|
1814
2696
|
}
|
|
1815
2697
|
|
|
2698
|
+
onHostDiagnostic(handler: HostDiagnosticListener): void {
|
|
2699
|
+
this.hostDiagnosticListeners.push(handler);
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
onDisconnected(handler: DisconnectListener): void {
|
|
2703
|
+
this.disconnectListeners.push(handler);
|
|
2704
|
+
}
|
|
2705
|
+
|
|
1816
2706
|
onDrop(handler: DropListener): void {
|
|
1817
2707
|
this.dropListeners.push(handler);
|
|
1818
2708
|
}
|
|
@@ -1828,53 +2718,116 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
|
|
|
1828
2718
|
|
|
1829
2719
|
@Injectable({ providedIn: "root" })
|
|
1830
2720
|
class AnQstBridgeRuntime {
|
|
2721
|
+
private static readonly maxDiagnostics = 50;
|
|
1831
2722
|
private adapter: BridgeAdapter | null = null;
|
|
1832
2723
|
private readonly slotHandlers = new Map<string, SlotHandler>();
|
|
1833
2724
|
private readonly outputHandlers = new Map<string, OutputHandler[]>();
|
|
1834
2725
|
private readonly dropHandlers = new Map<string, ((payload: unknown, x: number, y: number) => void)[]>();
|
|
1835
2726
|
private readonly hoverHandlers = new Map<string, ((payload: unknown, x: number, y: number) => void)[]>();
|
|
1836
2727
|
private readonly hoverLeftHandlers = new Map<string, (() => void)[]>();
|
|
1837
|
-
private readonly
|
|
2728
|
+
private readonly diagnosticListeners = new Set<(diagnostic: AnQstBridgeDiagnostic) => void>();
|
|
2729
|
+
private readonly _diagnostics = signal<readonly AnQstBridgeDiagnostic[]>([]);
|
|
2730
|
+
private readonly _state = signal<AnQstBridgeState>("starting");
|
|
2731
|
+
private readonly startup = this.init().catch((error) => {
|
|
2732
|
+
this._state.set("failed");
|
|
2733
|
+
this.reportFrontendDiagnostic({
|
|
2734
|
+
code: "BridgeBootstrapError",
|
|
2735
|
+
severity: "fatal",
|
|
2736
|
+
category: "bridge",
|
|
2737
|
+
recoverable: false,
|
|
2738
|
+
message: \`Failed to initialize bridge: \${errorMessage(error)}\`
|
|
2739
|
+
});
|
|
2740
|
+
throw error;
|
|
2741
|
+
});
|
|
2742
|
+
|
|
2743
|
+
diagnostics(): readonly AnQstBridgeDiagnostic[] {
|
|
2744
|
+
return this._diagnostics();
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
state(): AnQstBridgeState {
|
|
2748
|
+
return this._state();
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
subscribeDiagnostics(listener: (diagnostic: AnQstBridgeDiagnostic) => void): () => void {
|
|
2752
|
+
this.diagnosticListeners.add(listener);
|
|
2753
|
+
return () => this.diagnosticListeners.delete(listener);
|
|
2754
|
+
}
|
|
1838
2755
|
|
|
1839
2756
|
async ready(): Promise<void> {
|
|
1840
2757
|
return this.startup;
|
|
1841
2758
|
}
|
|
1842
2759
|
|
|
2760
|
+
reportFrontendDiagnostic(diagnostic: Omit<AnQstBridgeDiagnostic, "timestamp" | "source">): void {
|
|
2761
|
+
this.pushDiagnostic({
|
|
2762
|
+
...diagnostic,
|
|
2763
|
+
source: "frontend",
|
|
2764
|
+
transport: diagnostic.transport ?? this.adapter?.transport,
|
|
2765
|
+
timestamp: new Date().toISOString()
|
|
2766
|
+
});
|
|
2767
|
+
}
|
|
2768
|
+
|
|
1843
2769
|
async call<T>(service: string, member: string, args: unknown[]): Promise<T> {
|
|
1844
2770
|
const adapter = await this.requireAdapter();
|
|
1845
2771
|
return adapter.call<T>(service, member, args);
|
|
1846
2772
|
}
|
|
1847
2773
|
|
|
1848
2774
|
emit(service: string, member: string, args: unknown[]): void {
|
|
1849
|
-
|
|
1850
|
-
this.adapter.emit(service, member, args);
|
|
1851
|
-
return;
|
|
1852
|
-
}
|
|
1853
|
-
this.ready()
|
|
1854
|
-
.then(() => this.requireAdapterSync().emit(service, member, args))
|
|
1855
|
-
.catch((error) => console.error(error));
|
|
2775
|
+
this.publishNonCall("Emitter", service, member, (adapter) => adapter.emit(service, member, args));
|
|
1856
2776
|
}
|
|
1857
2777
|
|
|
1858
2778
|
setInput(service: string, member: string, value: unknown): void {
|
|
1859
|
-
|
|
1860
|
-
this.adapter.setInput(service, member, value);
|
|
1861
|
-
return;
|
|
1862
|
-
}
|
|
1863
|
-
this.ready()
|
|
1864
|
-
.then(() => this.requireAdapterSync().setInput(service, member, value))
|
|
1865
|
-
.catch((error) => console.error(error));
|
|
2779
|
+
this.publishNonCall("Input", service, member, (adapter) => adapter.setInput(service, member, value));
|
|
1866
2780
|
}
|
|
1867
2781
|
|
|
1868
2782
|
registerSlot(service: string, member: string, handler: SlotHandler): void {
|
|
1869
2783
|
const key = this.key(service, member);
|
|
1870
2784
|
this.slotHandlers.set(key, handler);
|
|
1871
2785
|
if (this.adapter !== null) {
|
|
1872
|
-
|
|
2786
|
+
try {
|
|
2787
|
+
this.adapter.registerSlot(service, member);
|
|
2788
|
+
} catch (error) {
|
|
2789
|
+
this.reportFrontendDiagnostic({
|
|
2790
|
+
code: "BridgePublishError",
|
|
2791
|
+
severity: "error",
|
|
2792
|
+
category: "bridge",
|
|
2793
|
+
recoverable: true,
|
|
2794
|
+
message: \`Failed to register Slot \${service}.\${member}: \${errorMessage(error)}\`,
|
|
2795
|
+
service,
|
|
2796
|
+
member,
|
|
2797
|
+
context: { interaction: "Slot" }
|
|
2798
|
+
});
|
|
2799
|
+
}
|
|
1873
2800
|
return;
|
|
1874
2801
|
}
|
|
1875
2802
|
this.ready()
|
|
1876
|
-
.then(() =>
|
|
1877
|
-
|
|
2803
|
+
.then(() => {
|
|
2804
|
+
try {
|
|
2805
|
+
this.requireAdapterSync().registerSlot(service, member);
|
|
2806
|
+
} catch (error) {
|
|
2807
|
+
this.reportFrontendDiagnostic({
|
|
2808
|
+
code: "BridgePublishError",
|
|
2809
|
+
severity: "error",
|
|
2810
|
+
category: "bridge",
|
|
2811
|
+
recoverable: true,
|
|
2812
|
+
message: \`Failed to register Slot \${service}.\${member}: \${errorMessage(error)}\`,
|
|
2813
|
+
service,
|
|
2814
|
+
member,
|
|
2815
|
+
context: { interaction: "Slot" }
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
2818
|
+
})
|
|
2819
|
+
.catch((error) => {
|
|
2820
|
+
this.reportFrontendDiagnostic({
|
|
2821
|
+
code: "BridgePublishError",
|
|
2822
|
+
severity: "error",
|
|
2823
|
+
category: "bridge",
|
|
2824
|
+
recoverable: true,
|
|
2825
|
+
message: \`Failed to register Slot \${service}.\${member}: \${errorMessage(error)}\`,
|
|
2826
|
+
service,
|
|
2827
|
+
member,
|
|
2828
|
+
context: { interaction: "Slot" }
|
|
2829
|
+
});
|
|
2830
|
+
});
|
|
1878
2831
|
}
|
|
1879
2832
|
|
|
1880
2833
|
onOutput(service: string, member: string, handler: OutputHandler): void {
|
|
@@ -1917,6 +2870,73 @@ class AnQstBridgeRuntime {
|
|
|
1917
2870
|
return this.requireAdapterSync();
|
|
1918
2871
|
}
|
|
1919
2872
|
|
|
2873
|
+
private pushDiagnostic(diagnostic: AnQstBridgeDiagnostic): void {
|
|
2874
|
+
const previous = this._diagnostics();
|
|
2875
|
+
const trimmed = previous.length >= AnQstBridgeRuntime.maxDiagnostics
|
|
2876
|
+
? previous.slice(previous.length - (AnQstBridgeRuntime.maxDiagnostics - 1))
|
|
2877
|
+
: previous;
|
|
2878
|
+
const next = [...trimmed, diagnostic];
|
|
2879
|
+
this._diagnostics.set(next);
|
|
2880
|
+
for (const listener of this.diagnosticListeners) {
|
|
2881
|
+
listener(diagnostic);
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2885
|
+
private publishNonCall(
|
|
2886
|
+
interaction: "Emitter" | "Input",
|
|
2887
|
+
service: string,
|
|
2888
|
+
member: string,
|
|
2889
|
+
publish: (adapter: BridgeAdapter) => void
|
|
2890
|
+
): void {
|
|
2891
|
+
if (this.adapter !== null) {
|
|
2892
|
+
try {
|
|
2893
|
+
publish(this.adapter);
|
|
2894
|
+
} catch (error) {
|
|
2895
|
+
this.reportFrontendDiagnostic({
|
|
2896
|
+
code: "BridgePublishError",
|
|
2897
|
+
severity: "error",
|
|
2898
|
+
category: "bridge",
|
|
2899
|
+
recoverable: true,
|
|
2900
|
+
message: \`Failed to publish \${interaction} \${service}.\${member}: \${errorMessage(error)}\`,
|
|
2901
|
+
service,
|
|
2902
|
+
member,
|
|
2903
|
+
context: { interaction }
|
|
2904
|
+
});
|
|
2905
|
+
}
|
|
2906
|
+
return;
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
this.ready()
|
|
2910
|
+
.then(() => {
|
|
2911
|
+
try {
|
|
2912
|
+
publish(this.requireAdapterSync());
|
|
2913
|
+
} catch (error) {
|
|
2914
|
+
this.reportFrontendDiagnostic({
|
|
2915
|
+
code: "BridgePublishError",
|
|
2916
|
+
severity: "error",
|
|
2917
|
+
category: "bridge",
|
|
2918
|
+
recoverable: true,
|
|
2919
|
+
message: \`Failed to publish \${interaction} \${service}.\${member}: \${errorMessage(error)}\`,
|
|
2920
|
+
service,
|
|
2921
|
+
member,
|
|
2922
|
+
context: { interaction }
|
|
2923
|
+
});
|
|
2924
|
+
}
|
|
2925
|
+
})
|
|
2926
|
+
.catch((error) => {
|
|
2927
|
+
this.reportFrontendDiagnostic({
|
|
2928
|
+
code: "BridgePublishError",
|
|
2929
|
+
severity: "error",
|
|
2930
|
+
category: "bridge",
|
|
2931
|
+
recoverable: true,
|
|
2932
|
+
message: \`Failed to publish \${interaction} \${service}.\${member}: \${errorMessage(error)}\`,
|
|
2933
|
+
service,
|
|
2934
|
+
member,
|
|
2935
|
+
context: { interaction }
|
|
2936
|
+
});
|
|
2937
|
+
});
|
|
2938
|
+
}
|
|
2939
|
+
|
|
1920
2940
|
private async init(): Promise<void> {
|
|
1921
2941
|
const anyWindow = window as unknown as { qt?: { webChannelTransport?: unknown }; QWebChannel?: QWebChannelCtor };
|
|
1922
2942
|
if (typeof anyWindow.QWebChannel === "function" && anyWindow.qt?.webChannelTransport !== undefined) {
|
|
@@ -1925,44 +2945,98 @@ class AnQstBridgeRuntime {
|
|
|
1925
2945
|
this.adapter = await WebSocketBridgeAdapter.create();
|
|
1926
2946
|
}
|
|
1927
2947
|
|
|
1928
|
-
this.adapter
|
|
2948
|
+
const adapter = this.adapter;
|
|
2949
|
+
adapter.onHostDiagnostic((payload) => {
|
|
2950
|
+
this.pushDiagnostic({
|
|
2951
|
+
...normalizeHostDiagnostic(payload, adapter.transport),
|
|
2952
|
+
timestamp: new Date().toISOString()
|
|
2953
|
+
});
|
|
2954
|
+
});
|
|
2955
|
+
adapter.onDisconnected(() => {
|
|
2956
|
+
this._state.set("disconnected");
|
|
2957
|
+
this.reportFrontendDiagnostic({
|
|
2958
|
+
code: "BridgeDisconnectedError",
|
|
2959
|
+
severity: "error",
|
|
2960
|
+
category: "bridge",
|
|
2961
|
+
recoverable: true,
|
|
2962
|
+
message: "Bridge disconnected.",
|
|
2963
|
+
transport: adapter.transport
|
|
2964
|
+
});
|
|
2965
|
+
});
|
|
2966
|
+
|
|
2967
|
+
adapter.onOutput((service, member, value) => {
|
|
1929
2968
|
const key = this.key(service, member);
|
|
1930
2969
|
for (const outputHandler of this.outputHandlers.get(key) ?? []) {
|
|
1931
2970
|
outputHandler(value);
|
|
1932
2971
|
}
|
|
1933
2972
|
});
|
|
1934
|
-
|
|
2973
|
+
adapter.onSlotInvocation(async (requestId, service, member, args) => {
|
|
1935
2974
|
const key = this.key(service, member);
|
|
1936
2975
|
const handler = this.slotHandlers.get(key);
|
|
1937
2976
|
if (handler === undefined) {
|
|
1938
|
-
this.
|
|
2977
|
+
this.reportFrontendDiagnostic({
|
|
2978
|
+
code: "HandlerNotRegisteredError",
|
|
2979
|
+
severity: "error",
|
|
2980
|
+
category: "bridge",
|
|
2981
|
+
recoverable: true,
|
|
2982
|
+
message: \`No slot handler registered for \${service}.\${member}.\`,
|
|
2983
|
+
service,
|
|
2984
|
+
member,
|
|
2985
|
+
requestId,
|
|
2986
|
+
context: { interaction: "Slot" }
|
|
2987
|
+
});
|
|
2988
|
+
adapter.resolveSlot(requestId, false, undefined, "No slot handler registered.");
|
|
1939
2989
|
return;
|
|
1940
2990
|
}
|
|
1941
2991
|
try {
|
|
1942
2992
|
const result = await Promise.resolve(handler(...args));
|
|
1943
2993
|
if (result instanceof Error) {
|
|
1944
|
-
this.
|
|
2994
|
+
this.reportFrontendDiagnostic({
|
|
2995
|
+
code: "SlotRequestFailed",
|
|
2996
|
+
severity: "error",
|
|
2997
|
+
category: "bridge",
|
|
2998
|
+
recoverable: true,
|
|
2999
|
+
message: result.message.length > 0
|
|
3000
|
+
? result.message
|
|
3001
|
+
: \`Slot \${service}.\${member} returned an Error.\`,
|
|
3002
|
+
service,
|
|
3003
|
+
member,
|
|
3004
|
+
requestId,
|
|
3005
|
+
context: { interaction: "Slot" }
|
|
3006
|
+
});
|
|
3007
|
+
adapter.resolveSlot(requestId, false, undefined, result.message);
|
|
1945
3008
|
return;
|
|
1946
3009
|
}
|
|
1947
|
-
|
|
3010
|
+
adapter.resolveSlot(requestId, true, result, "");
|
|
1948
3011
|
} catch (error) {
|
|
1949
|
-
const message =
|
|
1950
|
-
this.
|
|
3012
|
+
const message = errorMessage(error);
|
|
3013
|
+
this.reportFrontendDiagnostic({
|
|
3014
|
+
code: "SlotHandlerError",
|
|
3015
|
+
severity: "error",
|
|
3016
|
+
category: "bridge",
|
|
3017
|
+
recoverable: true,
|
|
3018
|
+
message: \`Slot handler \${service}.\${member} threw: \${message}\`,
|
|
3019
|
+
service,
|
|
3020
|
+
member,
|
|
3021
|
+
requestId,
|
|
3022
|
+
context: { interaction: "Slot" }
|
|
3023
|
+
});
|
|
3024
|
+
adapter.resolveSlot(requestId, false, undefined, message);
|
|
1951
3025
|
}
|
|
1952
3026
|
});
|
|
1953
|
-
|
|
3027
|
+
adapter.onDrop((service, member, payload, x, y) => {
|
|
1954
3028
|
const key = this.key(service, member);
|
|
1955
3029
|
for (const handler of this.dropHandlers.get(key) ?? []) {
|
|
1956
3030
|
handler(payload, x, y);
|
|
1957
3031
|
}
|
|
1958
3032
|
});
|
|
1959
|
-
|
|
3033
|
+
adapter.onHover((service, member, payload, x, y) => {
|
|
1960
3034
|
const key = this.key(service, member);
|
|
1961
3035
|
for (const handler of this.hoverHandlers.get(key) ?? []) {
|
|
1962
3036
|
handler(payload, x, y);
|
|
1963
3037
|
}
|
|
1964
3038
|
});
|
|
1965
|
-
|
|
3039
|
+
adapter.onHoverLeft((service, member) => {
|
|
1966
3040
|
const key = this.key(service, member);
|
|
1967
3041
|
for (const handler of this.hoverLeftHandlers.get(key) ?? []) {
|
|
1968
3042
|
handler();
|
|
@@ -1971,9 +3045,10 @@ class AnQstBridgeRuntime {
|
|
|
1971
3045
|
for (const key of this.slotHandlers.keys()) {
|
|
1972
3046
|
const parts = key.split("::");
|
|
1973
3047
|
if (parts.length === 2) {
|
|
1974
|
-
|
|
3048
|
+
adapter.registerSlot(parts[0], parts[1]);
|
|
1975
3049
|
}
|
|
1976
3050
|
}
|
|
3051
|
+
this._state.set("ready");
|
|
1977
3052
|
}
|
|
1978
3053
|
|
|
1979
3054
|
private key(service: string, member: string): string {
|
|
@@ -1981,6 +3056,23 @@ class AnQstBridgeRuntime {
|
|
|
1981
3056
|
}
|
|
1982
3057
|
|
|
1983
3058
|
}
|
|
3059
|
+
|
|
3060
|
+
@Injectable({ providedIn: "root" })
|
|
3061
|
+
export class AnQstBridgeDiagnostics {
|
|
3062
|
+
private readonly _bridge = inject(AnQstBridgeRuntime);
|
|
3063
|
+
|
|
3064
|
+
diagnostics(): readonly AnQstBridgeDiagnostic[] {
|
|
3065
|
+
return this._bridge.diagnostics();
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
state(): AnQstBridgeState {
|
|
3069
|
+
return this._bridge.state();
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
subscribe(listener: (diagnostic: AnQstBridgeDiagnostic) => void): () => void {
|
|
3073
|
+
return this._bridge.subscribeDiagnostics(listener);
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
1984
3076
|
${serviceClasses}
|
|
1985
3077
|
`;
|
|
1986
3078
|
}
|
|
@@ -1993,10 +3085,38 @@ function renderTsTypes(spec) {
|
|
|
1993
3085
|
function renderTypeServicesDts(spec) {
|
|
1994
3086
|
const externalTypeImports = renderRequiredTypeImports(spec, `frontend/${(0, layout_1.generatedFrontendDirName)(spec.widgetName)}/types/services.d.ts`).trim();
|
|
1995
3087
|
const localTypeImports = renderLocalTypeImports(spec).trim();
|
|
3088
|
+
const bridgeDiagnosticsDecl = `export type AnQstBridgeSeverity = "info" | "warn" | "error" | "fatal";
|
|
3089
|
+
|
|
3090
|
+
export type AnQstBridgeSource = "frontend" | "host";
|
|
3091
|
+
|
|
3092
|
+
export type AnQstBridgeTransport = "qt-webchannel" | "dev-websocket";
|
|
3093
|
+
|
|
3094
|
+
export type AnQstBridgeState = "starting" | "ready" | "failed" | "disconnected";
|
|
3095
|
+
|
|
3096
|
+
export interface AnQstBridgeDiagnostic {
|
|
3097
|
+
code: string;
|
|
3098
|
+
severity: AnQstBridgeSeverity;
|
|
3099
|
+
category: string;
|
|
3100
|
+
recoverable: boolean;
|
|
3101
|
+
message: string;
|
|
3102
|
+
timestamp: string;
|
|
3103
|
+
source: AnQstBridgeSource;
|
|
3104
|
+
transport?: AnQstBridgeTransport;
|
|
3105
|
+
service?: string;
|
|
3106
|
+
member?: string;
|
|
3107
|
+
requestId?: string;
|
|
3108
|
+
context?: Record<string, unknown>;
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
export declare class AnQstBridgeDiagnostics {
|
|
3112
|
+
diagnostics(): readonly AnQstBridgeDiagnostic[];
|
|
3113
|
+
state(): AnQstBridgeState;
|
|
3114
|
+
subscribe(listener: (diagnostic: AnQstBridgeDiagnostic) => void): () => void;
|
|
3115
|
+
}`;
|
|
1996
3116
|
const serviceDecls = spec.services
|
|
1997
3117
|
.map((s) => renderTsServiceDts(spec, s.name))
|
|
1998
3118
|
.join("\n\n");
|
|
1999
|
-
const sections = [externalTypeImports, localTypeImports, serviceDecls.trim()].filter((s) => s.length > 0);
|
|
3119
|
+
const sections = [externalTypeImports, localTypeImports, bridgeDiagnosticsDecl, serviceDecls.trim()].filter((s) => s.length > 0);
|
|
2000
3120
|
return sections.length > 0 ? `${sections.join("\n\n")}\n` : "";
|
|
2001
3121
|
}
|
|
2002
3122
|
function renderTypeTypesDts(spec) {
|
|
@@ -2071,7 +3191,7 @@ function renderNodeExpressWsTypes(spec) {
|
|
|
2071
3191
|
const sections = [typeImports, typeDecls].filter((s) => s.length > 0);
|
|
2072
3192
|
return sections.length > 0 ? `${sections.join("\n\n")}\n` : "";
|
|
2073
3193
|
}
|
|
2074
|
-
function renderNodeExpressWsIndex(spec) {
|
|
3194
|
+
function renderNodeExpressWsIndex(spec, codecCatalog) {
|
|
2075
3195
|
const typeImports = renderRequiredTypeImports(spec, `backend/node/express/${generatedNodeExpressWsDirName(spec.widgetName)}/index.ts`);
|
|
2076
3196
|
const typeDecls = renderTypeDeclarations(spec, true);
|
|
2077
3197
|
const handlerBridgeTypeName = `${spec.widgetName}HandlerBridge`;
|
|
@@ -2103,8 +3223,13 @@ function renderNodeExpressWsIndex(spec) {
|
|
|
2103
3223
|
.map((member) => {
|
|
2104
3224
|
const ret = mapTypeTextToTs(member.payloadTypeText ?? "void");
|
|
2105
3225
|
const args = nodeParamArgs(member);
|
|
3226
|
+
const paramSites = member.parameters.map((p) => (0, boundary_codecs_1.getBoundaryParameterSite)(codecCatalog, service.name, member.name, p.name));
|
|
3227
|
+
const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(codecCatalog, service.name, member.name);
|
|
3228
|
+
const encodedArgs = member.parameters.length > 0
|
|
3229
|
+
? `[${member.parameters.map((p, index) => `${paramSites[index] ? `encode${paramSites[index].codecId}(${p.name})` : p.name}`).join(", ")}]`
|
|
3230
|
+
: "[]";
|
|
2106
3231
|
return ` ${service.name}_${member.name}(${args}${args ? ", " : ""}timeoutMs = this.defaultSlotTimeoutMs): Promise<${ret}> {
|
|
2107
|
-
return this.invokeSlot("${service.name}", "${member.name}", ${
|
|
3232
|
+
return this.invokeSlot("${service.name}", "${member.name}", ${encodedArgs}, timeoutMs).then((value) => ${payloadSite ? `decode${payloadSite.codecId}(value)` : `value as ${ret}`});
|
|
2108
3233
|
}`;
|
|
2109
3234
|
}))
|
|
2110
3235
|
.join("\n");
|
|
@@ -2113,8 +3238,9 @@ function renderNodeExpressWsIndex(spec) {
|
|
|
2113
3238
|
.filter((member) => member.kind === "Output" && member.payloadTypeText)
|
|
2114
3239
|
.map((member) => {
|
|
2115
3240
|
const typeText = mapTypeTextToTs(member.payloadTypeText);
|
|
3241
|
+
const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(codecCatalog, service.name, member.name);
|
|
2116
3242
|
return ` set${service.name}_${nodeCap(member.name)}(value: ${typeText}): void {
|
|
2117
|
-
this.setOutputValue("${service.name}", "${member.name}", value);
|
|
3243
|
+
this.setOutputValue("${service.name}", "${member.name}", ${payloadSite ? `encode${payloadSite.codecId}(value)` : "value"});
|
|
2118
3244
|
}`;
|
|
2119
3245
|
}))
|
|
2120
3246
|
.join("\n");
|
|
@@ -2167,8 +3293,9 @@ function renderNodeExpressWsIndex(spec) {
|
|
|
2167
3293
|
.filter((member) => (member.kind === "Input" || member.kind === "Output") && member.payloadTypeText)
|
|
2168
3294
|
.map((member) => {
|
|
2169
3295
|
const typeText = mapTypeTextToTs(member.payloadTypeText);
|
|
3296
|
+
const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(codecCatalog, service.name, member.name);
|
|
2170
3297
|
if (member.kind === "Input") {
|
|
2171
|
-
return ` ${member.name}: {\n get: () => session.readInput("${service.name}", "${member.name}") as
|
|
3298
|
+
return ` ${member.name}: {\n get: () => session.readInput("${service.name}", "${member.name}").then((value) => ${payloadSite ? `decode${payloadSite.codecId}(value)` : `value as ${typeText}`}),\n on: (handler: (value: ${typeText}) => void) => session.onInput("${service.name}", "${member.name}", (value) => handler(${payloadSite ? `decode${payloadSite.codecId}(value)` : `value as ${typeText}`}))\n },`;
|
|
2172
3299
|
}
|
|
2173
3300
|
return ` ${member.name}: {\n set: (value: ${typeText}) => session.set${service.name}_${nodeCap(member.name)}(value)\n },`;
|
|
2174
3301
|
})
|
|
@@ -2180,6 +3307,11 @@ function renderNodeExpressWsIndex(spec) {
|
|
|
2180
3307
|
.flatMap((service) => service.members
|
|
2181
3308
|
.filter((member) => member.kind === "Call" && member.payloadTypeText)
|
|
2182
3309
|
.map((member) => {
|
|
3310
|
+
const paramSites = member.parameters.map((p) => (0, boundary_codecs_1.getBoundaryParameterSite)(codecCatalog, service.name, member.name, p.name));
|
|
3311
|
+
const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(codecCatalog, service.name, member.name);
|
|
3312
|
+
const decodedArgs = member.parameters.length > 0
|
|
3313
|
+
? member.parameters.map((p, index) => `${paramSites[index] ? `decode${paramSites[index].codecId}(args[${index}])` : `args[${index}] as ${mapTypeTextToTs(p.typeText)}`}`).join(", ")
|
|
3314
|
+
: "";
|
|
2183
3315
|
return ` if (service === "${service.name}" && member === "${member.name}") {
|
|
2184
3316
|
const handler = implementation.${service.name}.${member.name};
|
|
2185
3317
|
if (typeof handler !== "function") {
|
|
@@ -2202,8 +3334,8 @@ function renderNodeExpressWsIndex(spec) {
|
|
|
2202
3334
|
});
|
|
2203
3335
|
throw err;
|
|
2204
3336
|
}
|
|
2205
|
-
Promise.resolve(handler(buildHandlerBridge(session)
|
|
2206
|
-
.then((result) => sendJson(session.socket, { type: "callResult", requestId, result }))
|
|
3337
|
+
Promise.resolve(handler(buildHandlerBridge(session)${decodedArgs ? `, ${decodedArgs}` : ""}))
|
|
3338
|
+
.then((result) => sendJson(session.socket, { type: "callResult", requestId, result: ${payloadSite ? `encode${payloadSite.codecId}(result)` : "result"} }))
|
|
2207
3339
|
.catch((error) => {
|
|
2208
3340
|
const message = error instanceof Error ? error.message : String(error);
|
|
2209
3341
|
emitDiagnostic({
|
|
@@ -2231,6 +3363,13 @@ function renderNodeExpressWsIndex(spec) {
|
|
|
2231
3363
|
.flatMap((service) => service.members
|
|
2232
3364
|
.filter((member) => member.kind === "Emitter")
|
|
2233
3365
|
.map((member) => {
|
|
3366
|
+
const paramSites = member.parameters.map((p) => (0, boundary_codecs_1.getBoundaryParameterSite)(codecCatalog, service.name, member.name, p.name));
|
|
3367
|
+
const decodedArgs = member.parameters.length > 0
|
|
3368
|
+
? member.parameters.map((p, index) => `${paramSites[index] ? `decode${paramSites[index].codecId}(args[${index}])` : `args[${index}] as ${mapTypeTextToTs(p.typeText)}`}`).join(", ")
|
|
3369
|
+
: "";
|
|
3370
|
+
const decodedSignalArgs = member.parameters.length > 0
|
|
3371
|
+
? `[${member.parameters.map((p, index) => `${paramSites[index] ? `decode${paramSites[index].codecId}(args[${index}])` : `args[${index}] as ${mapTypeTextToTs(p.typeText)}`}`).join(", ")}]`
|
|
3372
|
+
: "[]";
|
|
2234
3373
|
return ` if (service === "${service.name}" && member === "${member.name}") {
|
|
2235
3374
|
const handler = implementation.${service.name}.${member.name};
|
|
2236
3375
|
if (typeof handler !== "function") {
|
|
@@ -2247,7 +3386,8 @@ function renderNodeExpressWsIndex(spec) {
|
|
|
2247
3386
|
});
|
|
2248
3387
|
throw err;
|
|
2249
3388
|
}
|
|
2250
|
-
|
|
3389
|
+
session.emitSignal(service, member, ${decodedSignalArgs});
|
|
3390
|
+
void Promise.resolve(handler(buildHandlerBridge(session)${decodedArgs ? `, ${decodedArgs}` : ""})).catch((error) => {
|
|
2251
3391
|
const message = error instanceof Error ? error.message : String(error);
|
|
2252
3392
|
emitDiagnostic({
|
|
2253
3393
|
code: "EmitterHandlerError",
|
|
@@ -2268,6 +3408,7 @@ function renderNodeExpressWsIndex(spec) {
|
|
|
2268
3408
|
.flatMap((service) => service.members
|
|
2269
3409
|
.filter((member) => member.kind === "Input" && member.payloadTypeText)
|
|
2270
3410
|
.map((member) => {
|
|
3411
|
+
const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(codecCatalog, service.name, member.name);
|
|
2271
3412
|
return ` if (service === "${service.name}" && member === "${member.name}") {
|
|
2272
3413
|
const handler = implementation.${service.name}.${member.name};
|
|
2273
3414
|
if (typeof handler !== "function") {
|
|
@@ -2284,7 +3425,9 @@ function renderNodeExpressWsIndex(spec) {
|
|
|
2284
3425
|
});
|
|
2285
3426
|
throw err;
|
|
2286
3427
|
}
|
|
2287
|
-
|
|
3428
|
+
const decodedValue = ${payloadSite ? `decode${payloadSite.codecId}(value)` : `value as ${mapTypeTextToTs(member.payloadTypeText)}`};
|
|
3429
|
+
session.setInputState(service, member, decodedValue);
|
|
3430
|
+
void Promise.resolve(handler(buildHandlerBridge(session), decodedValue)).catch((error) => {
|
|
2288
3431
|
const message = error instanceof Error ? error.message : String(error);
|
|
2289
3432
|
emitDiagnostic({
|
|
2290
3433
|
code: "InputHandlerError",
|
|
@@ -2306,6 +3449,9 @@ import type { WebSocket, WebSocketServer } from "ws";
|
|
|
2306
3449
|
${typeImports}
|
|
2307
3450
|
${typeDecls}
|
|
2308
3451
|
|
|
3452
|
+
// Boundary codec plan helpers
|
|
3453
|
+
${(0, boundary_codecs_1.renderTsBoundaryCodecHelpers)(codecCatalog)}
|
|
3454
|
+
|
|
2309
3455
|
${handlerInterfaces}
|
|
2310
3456
|
|
|
2311
3457
|
export interface ${spec.widgetName}NodeImplementation {
|
|
@@ -2672,7 +3818,6 @@ ${callDispatch}
|
|
|
2672
3818
|
const service = String(message.service ?? "");
|
|
2673
3819
|
const member = String(message.member ?? "");
|
|
2674
3820
|
const args = Array.isArray(message.args) ? (message.args as unknown[]) : [];
|
|
2675
|
-
session.emitSignal(service, member, args);
|
|
2676
3821
|
${emitterDispatch}
|
|
2677
3822
|
const err = new Error(\`No Emitter mapping found for \${service}.\${member}\`);
|
|
2678
3823
|
emitDiagnostic({
|
|
@@ -2691,7 +3836,6 @@ ${emitterDispatch}
|
|
|
2691
3836
|
const service = String(message.service ?? "");
|
|
2692
3837
|
const member = String(message.member ?? "");
|
|
2693
3838
|
const value = message.value;
|
|
2694
|
-
session.setInputState(service, member, value);
|
|
2695
3839
|
${inputDispatch}
|
|
2696
3840
|
const err = new Error(\`No Input mapping found for \${service}.\${member}\`);
|
|
2697
3841
|
emitDiagnostic({
|
|
@@ -2768,7 +3912,23 @@ function renderTypeRootIndexDts(spec) {
|
|
|
2768
3912
|
const typeDecls = renderTypeTypesDts(spec).trim();
|
|
2769
3913
|
const serviceDecls = renderTypeServicesDts(spec).trim();
|
|
2770
3914
|
const sections = [indexDecls, typeDecls, serviceDecls].filter((s) => s.length > 0);
|
|
2771
|
-
|
|
3915
|
+
if (sections.length === 0)
|
|
3916
|
+
return "";
|
|
3917
|
+
const dedupedLines = [];
|
|
3918
|
+
const seenImportLines = new Set();
|
|
3919
|
+
for (const line of sections.join("\n\n").split("\n")) {
|
|
3920
|
+
const trimmed = line.trim();
|
|
3921
|
+
if (!trimmed.startsWith("import type ")) {
|
|
3922
|
+
dedupedLines.push(line);
|
|
3923
|
+
continue;
|
|
3924
|
+
}
|
|
3925
|
+
if (seenImportLines.has(trimmed))
|
|
3926
|
+
continue;
|
|
3927
|
+
seenImportLines.add(trimmed);
|
|
3928
|
+
dedupedLines.push(line);
|
|
3929
|
+
}
|
|
3930
|
+
const deduped = dedupedLines.join("\n").replace(/\n{3,}/g, "\n\n");
|
|
3931
|
+
return `${deduped}\n`;
|
|
2772
3932
|
}
|
|
2773
3933
|
function generatedCppLibraryDirName(widgetName) {
|
|
2774
3934
|
return (0, layout_1.generatedQtWidgetDirName)(widgetName);
|
|
@@ -2781,10 +3941,11 @@ function generateOutputs(spec, options = { emitQWidget: true, emitAngularService
|
|
|
2781
3941
|
const cppDir = `backend/cpp/qt/${generatedCppLibraryDirName(spec.widgetName)}`;
|
|
2782
3942
|
const nodeDir = `backend/node/express/${generatedNodeExpressWsDirName(spec.widgetName)}`;
|
|
2783
3943
|
const outputs = {};
|
|
3944
|
+
const codecCatalog = (0, boundary_codecs_1.buildBoundaryCodecCatalog)(spec);
|
|
2784
3945
|
if (options.emitAngularService) {
|
|
2785
3946
|
outputs[`${frontendDir}/package.json`] = renderNpmPackage(spec);
|
|
2786
3947
|
outputs[`${frontendDir}/index.ts`] = renderTsIndex();
|
|
2787
|
-
outputs[`${frontendDir}/services.ts`] = renderTsServices(spec);
|
|
3948
|
+
outputs[`${frontendDir}/services.ts`] = renderTsServices(spec, codecCatalog);
|
|
2788
3949
|
outputs[`${frontendDir}/types.ts`] = renderTsTypes(spec);
|
|
2789
3950
|
outputs[`${frontendDir}/index.js`] = renderJsIndex();
|
|
2790
3951
|
outputs[`${frontendDir}/services.js`] = renderJsServices();
|
|
@@ -2798,13 +3959,13 @@ function generateOutputs(spec, options = { emitQWidget: true, emitAngularService
|
|
|
2798
3959
|
outputs[`${cppDir}/CMakeLists.txt`] = renderCMake(spec);
|
|
2799
3960
|
outputs[`${cppDir}/${spec.widgetName}.qrc`] = renderEmbeddedQrc(spec.widgetName, []);
|
|
2800
3961
|
outputs[`${cppDir}/include/${spec.widgetName}.h`] = renderWidgetUmbrellaHeader(spec);
|
|
2801
|
-
outputs[`${cppDir}/include/${spec.widgetName}Widget.h`] = renderWidgetHeader(spec, cppTypes);
|
|
3962
|
+
outputs[`${cppDir}/include/${spec.widgetName}Widget.h`] = renderWidgetHeader(spec, cppTypes, codecCatalog);
|
|
2802
3963
|
outputs[`${cppDir}/include/${spec.widgetName}Types.h`] = renderTypesHeader(spec, cppTypes);
|
|
2803
|
-
outputs[`${cppDir}/${spec.widgetName}.cpp`] = renderCppStub(spec, cppTypes);
|
|
3964
|
+
outputs[`${cppDir}/${spec.widgetName}.cpp`] = renderCppStub(spec, cppTypes, codecCatalog);
|
|
2804
3965
|
}
|
|
2805
3966
|
if (options.emitNodeExpressWs) {
|
|
2806
3967
|
outputs[`${nodeDir}/package.json`] = renderNodeExpressWsPackage(spec);
|
|
2807
|
-
outputs[`${nodeDir}/index.ts`] = renderNodeExpressWsIndex(spec);
|
|
3968
|
+
outputs[`${nodeDir}/index.ts`] = renderNodeExpressWsIndex(spec, codecCatalog);
|
|
2808
3969
|
outputs[`${nodeDir}/types/index.d.ts`] = renderNodeExpressWsTypes(spec);
|
|
2809
3970
|
}
|
|
2810
3971
|
return outputs;
|
|
@@ -2985,13 +4146,15 @@ function renderQtIntegrationCMake(widgetName) {
|
|
|
2985
4146
|
const generatedRootVar = "ANQST_GENERATED_WIDGET_DIR";
|
|
2986
4147
|
const generatedIncludeVar = "ANQST_GENERATED_INCLUDE_DIR";
|
|
2987
4148
|
const projectRootVar = "ANQST_PROJECT_ROOT";
|
|
4149
|
+
const requiredFilesVar = "ANQST_REQUIRED_GENERATED_FILES";
|
|
4150
|
+
const widgetBinaryDirVar = "ANQST_GENERATED_WIDGET_BINARY_DIR";
|
|
2988
4151
|
const widgetTarget = `${widgetName}Widget`;
|
|
2989
|
-
const autogenTarget = `${widgetTarget}_anqst_codegen`;
|
|
2990
4152
|
return `cmake_minimum_required(VERSION 3.21)
|
|
2991
4153
|
|
|
2992
4154
|
set(${projectRootVar} "\${CMAKE_CURRENT_LIST_DIR}/../../../../..")
|
|
2993
4155
|
set(${generatedRootVar} "\${CMAKE_CURRENT_LIST_DIR}/../qt/${generatedCppLibraryDirName(widgetName)}")
|
|
2994
4156
|
set(${generatedIncludeVar} "\${${generatedRootVar}}/include")
|
|
4157
|
+
set(${widgetBinaryDirVar} "\${CMAKE_CURRENT_BINARY_DIR}/${generatedCppLibraryDirName(widgetName)}")
|
|
2995
4158
|
|
|
2996
4159
|
if(TARGET ${widgetTarget})
|
|
2997
4160
|
return()
|
|
@@ -3001,66 +4164,27 @@ if(NOT TARGET anqstwebhostbase)
|
|
|
3001
4164
|
message(FATAL_ERROR "Target 'anqstwebhostbase' must exist before including generated AnQst CMake for ${widgetName}.")
|
|
3002
4165
|
endif()
|
|
3003
4166
|
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
set(CMAKE_AUTOUIC ON)
|
|
3007
|
-
set(CMAKE_AUTORCC ON)
|
|
3008
|
-
|
|
3009
|
-
find_program(ANQST_NPM_EXECUTABLE npm REQUIRED)
|
|
3010
|
-
find_program(ANQST_NPX_EXECUTABLE npx REQUIRED)
|
|
3011
|
-
|
|
3012
|
-
add_custom_command(
|
|
3013
|
-
OUTPUT
|
|
3014
|
-
"\${${generatedRootVar}}/CMakeLists.txt"
|
|
3015
|
-
"\${${generatedRootVar}}/${widgetName}.qrc"
|
|
3016
|
-
"\${${generatedRootVar}}/${widgetName}.cpp"
|
|
3017
|
-
"\${${generatedIncludeVar}}/${widgetName}.h"
|
|
3018
|
-
"\${${generatedIncludeVar}}/${widgetName}Widget.h"
|
|
3019
|
-
"\${${generatedIncludeVar}}/${widgetName}Types.h"
|
|
3020
|
-
"\${${generatedRootVar}}/webapp/index.html"
|
|
3021
|
-
COMMAND "\${ANQST_NPM_EXECUTABLE}" install
|
|
3022
|
-
COMMAND "\${ANQST_NPX_EXECUTABLE}" anqst build
|
|
3023
|
-
WORKING_DIRECTORY "\${${projectRootVar}}"
|
|
3024
|
-
COMMENT "Generating AnQst widget library (${widgetTarget}) from Angular project"
|
|
3025
|
-
VERBATIM
|
|
3026
|
-
)
|
|
3027
|
-
|
|
3028
|
-
add_custom_target(${autogenTarget}
|
|
3029
|
-
DEPENDS
|
|
3030
|
-
"\${${generatedRootVar}}/CMakeLists.txt"
|
|
3031
|
-
"\${${generatedRootVar}}/${widgetName}.qrc"
|
|
3032
|
-
"\${${generatedRootVar}}/${widgetName}.cpp"
|
|
3033
|
-
"\${${generatedIncludeVar}}/${widgetName}.h"
|
|
3034
|
-
"\${${generatedIncludeVar}}/${widgetName}Widget.h"
|
|
3035
|
-
"\${${generatedIncludeVar}}/${widgetName}Types.h"
|
|
3036
|
-
"\${${generatedRootVar}}/webapp/index.html"
|
|
3037
|
-
)
|
|
3038
|
-
|
|
3039
|
-
set_source_files_properties(
|
|
4167
|
+
set(${requiredFilesVar}
|
|
4168
|
+
"\${${generatedRootVar}}/CMakeLists.txt"
|
|
3040
4169
|
"\${${generatedRootVar}}/${widgetName}.qrc"
|
|
3041
4170
|
"\${${generatedRootVar}}/${widgetName}.cpp"
|
|
3042
4171
|
"\${${generatedIncludeVar}}/${widgetName}.h"
|
|
3043
4172
|
"\${${generatedIncludeVar}}/${widgetName}Widget.h"
|
|
3044
4173
|
"\${${generatedIncludeVar}}/${widgetName}Types.h"
|
|
3045
|
-
|
|
4174
|
+
"\${${generatedRootVar}}/webapp/index.html"
|
|
3046
4175
|
)
|
|
3047
4176
|
|
|
3048
|
-
|
|
3049
|
-
"\${
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
)
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
)
|
|
3060
|
-
target_link_libraries(${widgetTarget}
|
|
3061
|
-
PUBLIC
|
|
3062
|
-
anqstwebhostbase
|
|
3063
|
-
)
|
|
4177
|
+
foreach(required_file IN LISTS ${requiredFilesVar})
|
|
4178
|
+
if(NOT EXISTS "\${required_file}")
|
|
4179
|
+
message(FATAL_ERROR
|
|
4180
|
+
"Generated AnQst widget tree is incomplete for ${widgetName}. "
|
|
4181
|
+
"Missing file: \${required_file}. "
|
|
4182
|
+
"Run 'npx anqst build' in '\${${projectRootVar}}' first."
|
|
4183
|
+
)
|
|
4184
|
+
endif()
|
|
4185
|
+
endforeach()
|
|
4186
|
+
|
|
4187
|
+
add_subdirectory("\${${generatedRootVar}}" "\${${widgetBinaryDirVar}}")
|
|
3064
4188
|
`;
|
|
3065
4189
|
}
|
|
3066
4190
|
function installQtIntegrationCMake(cwd, widgetName) {
|