@dusted/anqst 1.0.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/src/app.js +103 -77
- package/dist/src/base93.js +124 -0
- package/dist/src/build-stamp.js +1 -1
- package/dist/src/codecgenerators/basecodecemitters/bigint-qint64/decoder.js +35 -0
- package/dist/src/codecgenerators/basecodecemitters/bigint-qint64/encoder.js +36 -0
- package/dist/src/codecgenerators/basecodecemitters/bigint-quint64/decoder.js +26 -0
- package/dist/src/codecgenerators/basecodecemitters/bigint-quint64/encoder.js +38 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-blob/decoder.js +28 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-blob/encoder.js +34 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-buffer/decoder.js +29 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-buffer/encoder.js +36 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-float32Array/decoder.js +46 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-float32Array/encoder.js +49 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-float64Array/decoder.js +46 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-float64Array/encoder.js +47 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-int16Array/decoder.js +46 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-int16Array/encoder.js +49 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-int32Array/decoder.js +50 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-int32Array/encoder.js +52 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-int8Array/decoder.js +38 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-int8Array/encoder.js +44 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-typedArray/decoder.js +33 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-typedArray/encoder.js +34 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-uint16Array/decoder.js +46 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-uint16Array/encoder.js +49 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-uint32Array/decoder.js +46 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-uint32Array/encoder.js +49 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-uint8Array/decoder.js +28 -0
- package/dist/src/codecgenerators/basecodecemitters/binary-uint8Array/encoder.js +34 -0
- package/dist/src/codecgenerators/basecodecemitters/boolean/decoder.js +34 -0
- package/dist/src/codecgenerators/basecodecemitters/boolean/encoder.js +40 -0
- package/dist/src/codecgenerators/basecodecemitters/dynamic-json/decoder.js +43 -0
- package/dist/src/codecgenerators/basecodecemitters/dynamic-json/encoder.js +45 -0
- package/dist/src/codecgenerators/basecodecemitters/dynamic-object/decoder.js +44 -0
- package/dist/src/codecgenerators/basecodecemitters/dynamic-object/encoder.js +46 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-int16/decoder.js +32 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-int16/encoder.js +43 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-int32/decoder.js +26 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-int32/encoder.js +37 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-int8/decoder.js +26 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-int8/encoder.js +37 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-qint16/decoder.js +36 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-qint16/encoder.js +36 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-qint32/decoder.js +25 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-qint32/encoder.js +36 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-qint8/decoder.js +36 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-qint8/encoder.js +36 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-quint16/decoder.js +26 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-quint16/encoder.js +38 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-quint32/decoder.js +27 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-quint32/encoder.js +39 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-quint8/decoder.js +26 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-quint8/encoder.js +38 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-uint16/decoder.js +30 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-uint16/encoder.js +42 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-uint32/decoder.js +31 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-uint32/encoder.js +43 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-uint8/decoder.js +30 -0
- package/dist/src/codecgenerators/basecodecemitters/integer-uint8/encoder.js +40 -0
- package/dist/src/codecgenerators/basecodecemitters/number/decoder.js +26 -0
- package/dist/src/codecgenerators/basecodecemitters/number/encoder.js +38 -0
- package/dist/src/codecgenerators/basecodecemitters/shared/comments.js +13 -0
- package/dist/src/codecgenerators/basecodecemitters/shared/contracts.js +2 -0
- package/dist/src/codecgenerators/basecodecemitters/shared/fixedwidth.js +53 -0
- package/dist/src/codecgenerators/basecodecemitters/shared/index.js +21 -0
- package/dist/src/codecgenerators/basecodecemitters/shared/positionalBase93.js +48 -0
- package/dist/src/codecgenerators/basecodecemitters/shared/rawbytes.js +30 -0
- package/dist/src/codecgenerators/basecodecemitters/string/decoder.js +43 -0
- package/dist/src/codecgenerators/basecodecemitters/string/encoder.js +43 -0
- package/dist/src/codecgenerators/basecodecemitters/stringArray/decoder.js +80 -0
- package/dist/src/codecgenerators/basecodecemitters/stringArray/encoder.js +57 -0
- package/dist/src/emit.js +760 -120
- package/dist/src/structured-top-level-codecs.js +1305 -0
- package/package.json +2 -3
- package/spec/AnQst-Spec-DSL.d.ts +15 -8
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 structured_top_level_codecs_1 = require("./structured-top-level-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,36 @@ 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
|
+
}
|
|
34
86
|
function mapTsTypeToCpp(typeText) {
|
|
35
87
|
const raw = typeText.trim();
|
|
36
88
|
if (/\bAnQst\.Type\.qint64\b/.test(raw))
|
|
@@ -55,6 +107,9 @@ function mapTsTypeToCpp(typeText) {
|
|
|
55
107
|
return "QString";
|
|
56
108
|
if (/\bAnQst\.Type\.json\b/.test(raw) || /\bAnQst\.Type\.object\b/.test(raw))
|
|
57
109
|
return "QVariantMap";
|
|
110
|
+
if (/\bAnQst\.Type\.(?:buffer|blob|typedArray|uint8Array|int8Array|uint16Array|int16Array|uint32Array|int32Array|float32Array|float64Array)\b/.test(raw)) {
|
|
111
|
+
return "QByteArray";
|
|
112
|
+
}
|
|
58
113
|
if (/\bAnQst\.Type\.(u?int(8|16|32))\b/.test(raw)) {
|
|
59
114
|
const narrowed = raw.match(/\bAnQst\.Type\.(u?int(?:8|16|32))\b/)?.[1];
|
|
60
115
|
if (narrowed === "int8")
|
|
@@ -83,6 +138,20 @@ function mapTsTypeToCpp(typeText) {
|
|
|
83
138
|
return "void";
|
|
84
139
|
if (t === "object")
|
|
85
140
|
return "QVariantMap";
|
|
141
|
+
if (t === "ArrayBuffer")
|
|
142
|
+
return "QByteArray";
|
|
143
|
+
if ([
|
|
144
|
+
"Uint8Array",
|
|
145
|
+
"Int8Array",
|
|
146
|
+
"Uint16Array",
|
|
147
|
+
"Int16Array",
|
|
148
|
+
"Uint32Array",
|
|
149
|
+
"Int32Array",
|
|
150
|
+
"Float32Array",
|
|
151
|
+
"Float64Array"
|
|
152
|
+
].includes(t)) {
|
|
153
|
+
return "QByteArray";
|
|
154
|
+
}
|
|
86
155
|
if (t.endsWith("[]")) {
|
|
87
156
|
return `QList<${mapTsTypeToCpp(t.slice(0, -2))}>`;
|
|
88
157
|
}
|
|
@@ -117,6 +186,8 @@ function variantToCppExpression(cppType, expr) {
|
|
|
117
186
|
return `${expr}.toStringList()`;
|
|
118
187
|
if (cppType === "QVariantMap")
|
|
119
188
|
return `${expr}.toMap()`;
|
|
189
|
+
if (cppType === "QByteArray")
|
|
190
|
+
return `${expr}.toByteArray()`;
|
|
120
191
|
if (cppType === "double")
|
|
121
192
|
return `${expr}.toDouble()`;
|
|
122
193
|
if (cppType === "bool")
|
|
@@ -140,6 +211,7 @@ function cppToVariantExpression(cppType, expr) {
|
|
|
140
211
|
if (cppType === "QString" ||
|
|
141
212
|
cppType === "QStringList" ||
|
|
142
213
|
cppType === "QVariantMap" ||
|
|
214
|
+
cppType === "QByteArray" ||
|
|
143
215
|
cppType === "double" ||
|
|
144
216
|
cppType === "bool" ||
|
|
145
217
|
cppType === "qint64" ||
|
|
@@ -398,6 +470,7 @@ class CppTypeNormalizer {
|
|
|
398
470
|
this.seedOrder = [];
|
|
399
471
|
this.allKnownNames = new Set();
|
|
400
472
|
this.usedNames = new Set();
|
|
473
|
+
this.syntheticNameByKey = new Map();
|
|
401
474
|
for (const decl of collectStructDecls(spec)) {
|
|
402
475
|
this.allKnownNames.add(decl.name);
|
|
403
476
|
this.usedNames.add(decl.name);
|
|
@@ -459,6 +532,16 @@ class CppTypeNormalizer {
|
|
|
459
532
|
return this.mapTypeNode(typeNode.type, nameHintParts, deps);
|
|
460
533
|
}
|
|
461
534
|
if (typescript_1.default.isUnionTypeNode(typeNode)) {
|
|
535
|
+
const filtered = filterNullishUnionTypeNodes(typeNode.types);
|
|
536
|
+
if (filtered.length === 1) {
|
|
537
|
+
return this.mapTypeNode(filtered[0], nameHintParts, deps);
|
|
538
|
+
}
|
|
539
|
+
if (isStringLikeUnionTypeNode(typeNode))
|
|
540
|
+
return "QString";
|
|
541
|
+
if (isBooleanLikeUnionTypeNode(typeNode))
|
|
542
|
+
return "bool";
|
|
543
|
+
if (isNumberLikeUnionTypeNode(typeNode))
|
|
544
|
+
return "double";
|
|
462
545
|
return "QString";
|
|
463
546
|
}
|
|
464
547
|
if (typescript_1.default.isTypeLiteralNode(typeNode)) {
|
|
@@ -506,7 +589,14 @@ class CppTypeNormalizer {
|
|
|
506
589
|
}
|
|
507
590
|
ensureSyntheticStruct(typeNode, nameHintParts, deps) {
|
|
508
591
|
const baseName = this.makeSyntheticBaseName(nameHintParts);
|
|
592
|
+
const syntheticKey = `${baseName}::${typeNode.getText()}`;
|
|
593
|
+
const existingName = this.syntheticNameByKey.get(syntheticKey);
|
|
594
|
+
if (existingName) {
|
|
595
|
+
deps.add(existingName);
|
|
596
|
+
return existingName;
|
|
597
|
+
}
|
|
509
598
|
const synthesizedName = this.allocateUniqueName(baseName);
|
|
599
|
+
this.syntheticNameByKey.set(syntheticKey, synthesizedName);
|
|
510
600
|
if (this.declMap.has(synthesizedName)) {
|
|
511
601
|
deps.add(synthesizedName);
|
|
512
602
|
return synthesizedName;
|
|
@@ -647,6 +737,29 @@ function collectDragDropMimeConstants(spec) {
|
|
|
647
737
|
}
|
|
648
738
|
return constants;
|
|
649
739
|
}
|
|
740
|
+
function collectDragDropPayloadHelpers(spec, cppTypes, cppCodecCatalog) {
|
|
741
|
+
const seen = new Set();
|
|
742
|
+
const helpers = [];
|
|
743
|
+
for (const service of spec.services) {
|
|
744
|
+
for (const member of service.members) {
|
|
745
|
+
if ((member.kind !== "DropTarget" && member.kind !== "HoverTarget") || !member.payloadTypeText)
|
|
746
|
+
continue;
|
|
747
|
+
const typeName = member.payloadTypeText.replace(/\s/g, "");
|
|
748
|
+
if (seen.has(typeName))
|
|
749
|
+
continue;
|
|
750
|
+
seen.add(typeName);
|
|
751
|
+
const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(cppCodecCatalog, service.name, member.name);
|
|
752
|
+
if (!payloadSite)
|
|
753
|
+
continue;
|
|
754
|
+
helpers.push({
|
|
755
|
+
typeName,
|
|
756
|
+
cppType: cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]),
|
|
757
|
+
codecId: payloadSite.codecId
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return helpers;
|
|
762
|
+
}
|
|
650
763
|
function renderTypesHeader(spec, cppTypes) {
|
|
651
764
|
const decls = cppTypes.orderedDecls.map(renderCppDecl).join("\n\n");
|
|
652
765
|
const metatypes = cppTypes.structNames
|
|
@@ -663,6 +776,7 @@ function renderTypesHeader(spec, cppTypes) {
|
|
|
663
776
|
return `#pragma once
|
|
664
777
|
#include <QString>
|
|
665
778
|
#include <QStringList>
|
|
779
|
+
#include <QByteArray>
|
|
666
780
|
#include <QList>
|
|
667
781
|
#include <QVariantMap>
|
|
668
782
|
#include <QMetaType>
|
|
@@ -687,6 +801,8 @@ function renderWidgetUmbrellaHeader(spec) {
|
|
|
687
801
|
}
|
|
688
802
|
function renderWidgetHeader(spec, cppTypes) {
|
|
689
803
|
const widgetClassName = `${spec.widgetName}Widget`;
|
|
804
|
+
const cppCodecCatalog = (0, structured_top_level_codecs_1.buildStructuredCodecCatalog)(spec);
|
|
805
|
+
const dragDropPayloadHelpers = collectDragDropPayloadHelpers(spec, cppTypes, cppCodecCatalog);
|
|
690
806
|
const callbackAliases = [];
|
|
691
807
|
const publicMethods = [];
|
|
692
808
|
const slotMethods = [];
|
|
@@ -696,6 +812,10 @@ function renderWidgetHeader(spec, cppTypes) {
|
|
|
696
812
|
const properties = [];
|
|
697
813
|
const fields = [];
|
|
698
814
|
const publicSlots = [];
|
|
815
|
+
const dragDropHelperMethods = dragDropPayloadHelpers.flatMap((helper) => [
|
|
816
|
+
`static QByteArray encodeDragDropPayload_${helper.typeName}(const ${helper.cppType}& payload);`,
|
|
817
|
+
`static std::optional<${helper.cppType}> decodeDragDropPayload_${helper.typeName}(const QByteArray& rawPayload);`
|
|
818
|
+
]);
|
|
699
819
|
const bindings = [];
|
|
700
820
|
for (const service of spec.services) {
|
|
701
821
|
for (const member of service.members) {
|
|
@@ -747,6 +867,7 @@ function renderWidgetHeader(spec, cppTypes) {
|
|
|
747
867
|
}
|
|
748
868
|
}
|
|
749
869
|
return `#pragma once
|
|
870
|
+
#include <QByteArray>
|
|
750
871
|
#include <QDateTime>
|
|
751
872
|
#include <QHash>
|
|
752
873
|
#include <QMetaMethod>
|
|
@@ -754,6 +875,7 @@ function renderWidgetHeader(spec, cppTypes) {
|
|
|
754
875
|
#include <QVariant>
|
|
755
876
|
#include <QVariantList>
|
|
756
877
|
#include <functional>
|
|
878
|
+
#include <optional>
|
|
757
879
|
#include "AnQstWebHostBase.h"
|
|
758
880
|
#include "${spec.widgetName}Types.h"
|
|
759
881
|
|
|
@@ -784,6 +906,7 @@ ${handleMethods.join("\n")}
|
|
|
784
906
|
static constexpr const char* kBootstrapContentRoot = "qrc:/${spec.widgetName.toLowerCase()}";
|
|
785
907
|
static constexpr const char* kBootstrapBridgeObject = "${spec.widgetName}Bridge";
|
|
786
908
|
static constexpr int kMaxQueuedCallsPerEndpoint = 1024;
|
|
909
|
+
${dragDropHelperMethods.map((s) => ` ${s}`).join("\n")}
|
|
787
910
|
|
|
788
911
|
handle handle;
|
|
789
912
|
${publicMethods.map((s) => ` ${s}`).join("\n")}
|
|
@@ -836,13 +959,22 @@ ${fields.map((f) => ` ${f}`).join("\n")}
|
|
|
836
959
|
}
|
|
837
960
|
function renderCppStub(spec, cppTypes) {
|
|
838
961
|
const widgetClassName = `${spec.widgetName}Widget`;
|
|
962
|
+
const cppCodecCatalog = (0, structured_top_level_codecs_1.buildStructuredCodecCatalog)(spec);
|
|
963
|
+
const dragDropPayloadHelpers = collectDragDropPayloadHelpers(spec, cppTypes, cppCodecCatalog);
|
|
964
|
+
const cppCodecHelpers = (0, structured_top_level_codecs_1.renderCppStructuredCodecHelpers)(cppCodecCatalog, (typeText, pathHintParts) => cppTypes.mapTypeText(typeText, pathHintParts)).trim();
|
|
839
965
|
const lines = [];
|
|
840
966
|
lines.push(`#include "include/${spec.widgetName}Widget.h"`);
|
|
841
967
|
lines.push(`#include <QDebug>`);
|
|
842
968
|
lines.push(`#include <QElapsedTimer>`);
|
|
843
969
|
lines.push(`#include <QEventLoop>`);
|
|
970
|
+
lines.push(`#include <QJsonArray>`);
|
|
971
|
+
lines.push(`#include <QJsonDocument>`);
|
|
844
972
|
lines.push(`#include <QMetaType>`);
|
|
845
973
|
lines.push(`#include <QTimer>`);
|
|
974
|
+
lines.push(`#include <cstring>`);
|
|
975
|
+
lines.push(`#include <cstdint>`);
|
|
976
|
+
lines.push(`#include <string>`);
|
|
977
|
+
lines.push(`#include <vector>`);
|
|
846
978
|
lines.push(`#include <stdexcept>`);
|
|
847
979
|
lines.push("");
|
|
848
980
|
lines.push(`using namespace ${spec.widgetName};`);
|
|
@@ -860,8 +992,31 @@ function renderCppStub(spec, cppTypes) {
|
|
|
860
992
|
lines.push(" }();");
|
|
861
993
|
lines.push(" Q_UNUSED(registered);");
|
|
862
994
|
lines.push("}");
|
|
995
|
+
if (cppCodecHelpers.length > 0) {
|
|
996
|
+
lines.push("");
|
|
997
|
+
lines.push(cppCodecHelpers);
|
|
998
|
+
}
|
|
863
999
|
lines.push("}");
|
|
864
1000
|
lines.push("");
|
|
1001
|
+
for (const helper of dragDropPayloadHelpers) {
|
|
1002
|
+
lines.push(`QByteArray ${widgetClassName}::encodeDragDropPayload_${helper.typeName}(const ${helper.cppType}& payload) {`);
|
|
1003
|
+
lines.push(` return QJsonDocument::fromVariant(anqstNormalizeWireItems(encode${helper.codecId}(payload))).toJson(QJsonDocument::Compact);`);
|
|
1004
|
+
lines.push(`}`);
|
|
1005
|
+
lines.push("");
|
|
1006
|
+
lines.push(`std::optional<${helper.cppType}> ${widgetClassName}::decodeDragDropPayload_${helper.typeName}(const QByteArray& rawPayload) {`);
|
|
1007
|
+
lines.push(` QJsonParseError parseError;`);
|
|
1008
|
+
lines.push(` const QJsonDocument document = QJsonDocument::fromJson(rawPayload, &parseError);`);
|
|
1009
|
+
lines.push(` if (parseError.error != QJsonParseError::NoError || !document.isArray()) {`);
|
|
1010
|
+
lines.push(` return std::nullopt;`);
|
|
1011
|
+
lines.push(` }`);
|
|
1012
|
+
lines.push(` try {`);
|
|
1013
|
+
lines.push(` return decode${helper.codecId}(document.array().toVariantList());`);
|
|
1014
|
+
lines.push(` } catch (...) {`);
|
|
1015
|
+
lines.push(` return std::nullopt;`);
|
|
1016
|
+
lines.push(` }`);
|
|
1017
|
+
lines.push(`}`);
|
|
1018
|
+
lines.push("");
|
|
1019
|
+
}
|
|
865
1020
|
for (const service of spec.services) {
|
|
866
1021
|
for (const member of service.members) {
|
|
867
1022
|
if (member.kind !== "Call" || !member.payloadTypeText)
|
|
@@ -912,17 +1067,19 @@ function renderCppStub(spec, cppTypes) {
|
|
|
912
1067
|
for (const member of service.members) {
|
|
913
1068
|
if (member.kind === "DropTarget" && member.payloadTypeText) {
|
|
914
1069
|
const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
|
|
1070
|
+
const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(cppCodecCatalog, service.name, member.name);
|
|
915
1071
|
lines.push(` QObject::connect(this, &AnQstWebHostBase::anQstBridge_dropReceived, this, [this](const QString& service, const QString& member, const QVariant& payload, double x, double y) {`);
|
|
916
1072
|
lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
|
|
917
|
-
lines.push(` emit ${member.name}(payload.value<${cppType}>(), x, y);`);
|
|
1073
|
+
lines.push(` emit ${member.name}(${payloadSite ? `decode${payloadSite.codecId}(payload)` : `payload.value<${cppType}>()`}, x, y);`);
|
|
918
1074
|
lines.push(` }`);
|
|
919
1075
|
lines.push(` });`);
|
|
920
1076
|
}
|
|
921
1077
|
else if (member.kind === "HoverTarget" && member.payloadTypeText) {
|
|
922
1078
|
const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
|
|
1079
|
+
const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(cppCodecCatalog, service.name, member.name);
|
|
923
1080
|
lines.push(` QObject::connect(this, &AnQstWebHostBase::anQstBridge_hoverUpdated, this, [this](const QString& service, const QString& member, const QVariant& payload, double x, double y) {`);
|
|
924
1081
|
lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
|
|
925
|
-
lines.push(` emit ${member.name}(payload.value<${cppType}>(), x, y);`);
|
|
1082
|
+
lines.push(` emit ${member.name}(${payloadSite ? `decode${payloadSite.codecId}(payload)` : `payload.value<${cppType}>()`}, x, y);`);
|
|
926
1083
|
lines.push(` }`);
|
|
927
1084
|
lines.push(` });`);
|
|
928
1085
|
lines.push(` QObject::connect(this, &AnQstWebHostBase::anQstBridge_hoverLeft, this, [this](const QString& service, const QString& member) {`);
|
|
@@ -1028,11 +1185,13 @@ function renderCppStub(spec, cppTypes) {
|
|
|
1028
1185
|
continue;
|
|
1029
1186
|
const timeoutMs = member.timeoutMs;
|
|
1030
1187
|
const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
|
|
1188
|
+
const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(cppCodecCatalog, service.name, member.name);
|
|
1031
1189
|
lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
|
|
1032
1190
|
for (let i = 0; i < member.parameters.length; i++) {
|
|
1033
1191
|
const p = member.parameters[i];
|
|
1034
1192
|
const pType = cppTypes.mapTypeText(p.typeText, [service.name, member.name, p.name]);
|
|
1035
|
-
|
|
1193
|
+
const paramSite = (0, structured_top_level_codecs_1.getStructuredParameterSite)(cppCodecCatalog, service.name, member.name, p.name);
|
|
1194
|
+
lines.push(` const ${pType} ${p.name} = ${paramSite ? `decode${paramSite.codecId}(args.value(${i}))` : variantToCppExpression(pType, `args.value(${i})`)};`);
|
|
1036
1195
|
}
|
|
1037
1196
|
lines.push(` const QString requestId = QStringLiteral("call-%1").arg(++m_callRequestCounter);`);
|
|
1038
1197
|
lines.push(` const QString queueKey = makeBindingKey(QStringLiteral("${service.name}"), QStringLiteral("${member.name}"));`);
|
|
@@ -1049,7 +1208,7 @@ function renderCppStub(spec, cppTypes) {
|
|
|
1049
1208
|
lines.push(` try {`);
|
|
1050
1209
|
const callArgs = member.parameters.map((p) => p.name).join(", ");
|
|
1051
1210
|
lines.push(` const ${cppType} result = m_${member.name}Handler(${callArgs});`);
|
|
1052
|
-
lines.push(` return ${cppToVariantExpression(cppType, "result")};`);
|
|
1211
|
+
lines.push(` return ${payloadSite ? `encode${payloadSite.codecId}(result)` : cppToVariantExpression(cppType, "result")};`);
|
|
1053
1212
|
lines.push(` } catch (const std::exception& ex) {`);
|
|
1054
1213
|
lines.push(` return QVariantMap{`);
|
|
1055
1214
|
lines.push(` {QStringLiteral("code"), QStringLiteral("CallHandlerError")},`);
|
|
@@ -1080,7 +1239,13 @@ function renderCppStub(spec, cppTypes) {
|
|
|
1080
1239
|
lines.push(` }`);
|
|
1081
1240
|
}
|
|
1082
1241
|
}
|
|
1083
|
-
lines.push(` return
|
|
1242
|
+
lines.push(` return QVariantMap{`);
|
|
1243
|
+
lines.push(` {QStringLiteral("code"), QStringLiteral("HandlerNotRegisteredError")},`);
|
|
1244
|
+
lines.push(` {QStringLiteral("message"), QStringLiteral("No Call mapping found.")},`);
|
|
1245
|
+
lines.push(` {QStringLiteral("service"), service},`);
|
|
1246
|
+
lines.push(` {QStringLiteral("member"), member},`);
|
|
1247
|
+
lines.push(` {QStringLiteral("requestId"), QString()}`);
|
|
1248
|
+
lines.push(` };`);
|
|
1084
1249
|
lines.push(`}`);
|
|
1085
1250
|
lines.push("");
|
|
1086
1251
|
lines.push(`void ${widgetClassName}::handleGeneratedEmitter(const QString& service, const QString& member, const QVariantList& args) {`);
|
|
@@ -1095,7 +1260,8 @@ function renderCppStub(spec, cppTypes) {
|
|
|
1095
1260
|
for (let i = 0; i < member.parameters.length; i++) {
|
|
1096
1261
|
const p = member.parameters[i];
|
|
1097
1262
|
const pType = cppTypes.mapTypeText(p.typeText, [service.name, member.name, p.name]);
|
|
1098
|
-
|
|
1263
|
+
const paramSite = (0, structured_top_level_codecs_1.getStructuredParameterSite)(cppCodecCatalog, service.name, member.name, p.name);
|
|
1264
|
+
lines.push(` const ${pType} ${p.name} = ${paramSite ? `decode${paramSite.codecId}(args.value(${i}))` : variantToCppExpression(pType, `args.value(${i})`)};`);
|
|
1099
1265
|
}
|
|
1100
1266
|
const argNames = member.parameters.map((p) => p.name).join(", ");
|
|
1101
1267
|
lines.push(` emit ${member.name}(${argNames});`);
|
|
@@ -1111,8 +1277,9 @@ function renderCppStub(spec, cppTypes) {
|
|
|
1111
1277
|
if (member.kind !== "Input" || !member.payloadTypeText)
|
|
1112
1278
|
continue;
|
|
1113
1279
|
const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
|
|
1280
|
+
const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(cppCodecCatalog, service.name, member.name);
|
|
1114
1281
|
lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
|
|
1115
|
-
lines.push(` const ${cppType} typedValue = ${variantToCppExpression(cppType, "value")};`);
|
|
1282
|
+
lines.push(` const ${cppType} typedValue = ${payloadSite ? `decode${payloadSite.codecId}(value)` : variantToCppExpression(cppType, "value")};`);
|
|
1116
1283
|
lines.push(` set${pascalCase(member.name)}(typedValue);`);
|
|
1117
1284
|
lines.push(` if (m_${member.name}Handler) m_${member.name}Handler(typedValue);`);
|
|
1118
1285
|
lines.push(` return;`);
|
|
@@ -1132,12 +1299,14 @@ function renderCppStub(spec, cppTypes) {
|
|
|
1132
1299
|
}
|
|
1133
1300
|
if (member.kind === "Slot") {
|
|
1134
1301
|
const ret = member.payloadTypeText ? cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]) : "void";
|
|
1302
|
+
const payloadSite = member.payloadTypeText ? (0, structured_top_level_codecs_1.getStructuredPayloadSite)(cppCodecCatalog, service.name, member.name) : undefined;
|
|
1135
1303
|
const args = member.parameters.map((p) => `${cppTypes.mapTypeText(p.typeText, [service.name, member.name, p.name])} ${p.name}`).join(", ");
|
|
1136
1304
|
lines.push(`${ret} ${widgetClassName}::slot_${member.name}(${args}) {`);
|
|
1137
1305
|
lines.push(` QVariantList invokeArgs;`);
|
|
1138
1306
|
for (const p of member.parameters) {
|
|
1139
1307
|
const pType = mapTsTypeToCpp(p.typeText);
|
|
1140
|
-
|
|
1308
|
+
const paramSite = (0, structured_top_level_codecs_1.getStructuredParameterSite)(cppCodecCatalog, service.name, member.name, p.name);
|
|
1309
|
+
lines.push(` invokeArgs.push_back(${paramSite ? `encode${paramSite.codecId}(${p.name})` : cppToVariantExpression(pType, p.name)});`);
|
|
1141
1310
|
}
|
|
1142
1311
|
lines.push(` QVariant result;`);
|
|
1143
1312
|
lines.push(` QString invokeError;`);
|
|
@@ -1154,13 +1323,14 @@ function renderCppStub(spec, cppTypes) {
|
|
|
1154
1323
|
lines.push(` return;`);
|
|
1155
1324
|
}
|
|
1156
1325
|
else {
|
|
1157
|
-
lines.push(` return ${variantToCppExpression(ret, "result")};`);
|
|
1326
|
+
lines.push(` return ${payloadSite ? `decode${payloadSite.codecId}(result)` : variantToCppExpression(ret, "result")};`);
|
|
1158
1327
|
}
|
|
1159
1328
|
lines.push("}");
|
|
1160
1329
|
lines.push("");
|
|
1161
1330
|
}
|
|
1162
1331
|
else if ((member.kind === "Input" || member.kind === "Output") && member.payloadTypeText) {
|
|
1163
1332
|
const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
|
|
1333
|
+
const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(cppCodecCatalog, service.name, member.name);
|
|
1164
1334
|
const cap = member.name.charAt(0).toUpperCase() + member.name.slice(1);
|
|
1165
1335
|
lines.push(`${cppType} ${widgetClassName}::${member.name}() const {`);
|
|
1166
1336
|
lines.push(` return m_${member.name};`);
|
|
@@ -1168,9 +1338,40 @@ function renderCppStub(spec, cppTypes) {
|
|
|
1168
1338
|
lines.push("");
|
|
1169
1339
|
lines.push(`void ${widgetClassName}::set${cap}(const ${cppType}& value) {`);
|
|
1170
1340
|
lines.push(` if (m_${member.name} == value) return;`);
|
|
1341
|
+
if (member.kind === "Output") {
|
|
1342
|
+
lines.push(` QVariant encodedValue;`);
|
|
1343
|
+
lines.push(` try {`);
|
|
1344
|
+
lines.push(` encodedValue = ${payloadSite ? `encode${payloadSite.codecId}(value)` : cppToVariantExpression(cppType, "value")};`);
|
|
1345
|
+
lines.push(` } catch (const std::exception& ex) {`);
|
|
1346
|
+
lines.push(` emitHostError(`);
|
|
1347
|
+
lines.push(` QStringLiteral("SerializationError"),`);
|
|
1348
|
+
lines.push(` QStringLiteral("bridge"),`);
|
|
1349
|
+
lines.push(` QStringLiteral("error"),`);
|
|
1350
|
+
lines.push(` true,`);
|
|
1351
|
+
lines.push(` QStringLiteral("Failed to serialize Output ${service.name}.${member.name}."),`);
|
|
1352
|
+
lines.push(` {`);
|
|
1353
|
+
lines.push(` {QStringLiteral("service"), QStringLiteral("${service.name}")},`);
|
|
1354
|
+
lines.push(` {QStringLiteral("member"), QStringLiteral("${member.name}")},`);
|
|
1355
|
+
lines.push(` {QStringLiteral("detail"), QString::fromUtf8(ex.what())},`);
|
|
1356
|
+
lines.push(` });`);
|
|
1357
|
+
lines.push(` return;`);
|
|
1358
|
+
lines.push(` } catch (...) {`);
|
|
1359
|
+
lines.push(` emitHostError(`);
|
|
1360
|
+
lines.push(` QStringLiteral("SerializationError"),`);
|
|
1361
|
+
lines.push(` QStringLiteral("bridge"),`);
|
|
1362
|
+
lines.push(` QStringLiteral("error"),`);
|
|
1363
|
+
lines.push(` true,`);
|
|
1364
|
+
lines.push(` QStringLiteral("Failed to serialize Output ${service.name}.${member.name}."),`);
|
|
1365
|
+
lines.push(` {`);
|
|
1366
|
+
lines.push(` {QStringLiteral("service"), QStringLiteral("${service.name}")},`);
|
|
1367
|
+
lines.push(` {QStringLiteral("member"), QStringLiteral("${member.name}")},`);
|
|
1368
|
+
lines.push(` });`);
|
|
1369
|
+
lines.push(` return;`);
|
|
1370
|
+
lines.push(` }`);
|
|
1371
|
+
}
|
|
1171
1372
|
lines.push(` m_${member.name} = value;`);
|
|
1172
1373
|
if (member.kind === "Output") {
|
|
1173
|
-
lines.push(` setOutputValue(QStringLiteral("${service.name}"), QStringLiteral("${member.name}"),
|
|
1374
|
+
lines.push(` setOutputValue(QStringLiteral("${service.name}"), QStringLiteral("${member.name}"), encodedValue);`);
|
|
1174
1375
|
}
|
|
1175
1376
|
lines.push(` emit ${member.name}Changed(value);`);
|
|
1176
1377
|
lines.push("}");
|
|
@@ -1357,59 +1558,155 @@ function slotHandlerReturnType(tsRet) {
|
|
|
1357
1558
|
}
|
|
1358
1559
|
return `${tsRet} | Promise<${tsRet}> | Error`;
|
|
1359
1560
|
}
|
|
1360
|
-
function renderTsService(spec, serviceName) {
|
|
1561
|
+
function renderTsService(spec, serviceName, codecCatalog) {
|
|
1361
1562
|
const members = spec.services.find((s) => s.name === serviceName)?.members ?? [];
|
|
1362
1563
|
const fieldLines = [];
|
|
1363
1564
|
const methodLines = [];
|
|
1364
1565
|
const setMembers = [];
|
|
1365
1566
|
const onSlotMembers = [];
|
|
1366
1567
|
const constructorBodyLines = [];
|
|
1367
|
-
constructorBodyLines.push(" this._bridge.ready().catch((error) => console.error('AnQst bridge ready() failed', error, (error as { stack?: unknown })?.stack));");
|
|
1368
1568
|
for (const m of members) {
|
|
1369
1569
|
const args = m.parameters.map((p) => `${p.name}: ${mapTypeTextToTs(p.typeText)}`).join(", ");
|
|
1370
|
-
const
|
|
1371
|
-
const
|
|
1570
|
+
const paramSites = m.parameters.map((p) => (0, structured_top_level_codecs_1.getStructuredParameterSite)(codecCatalog, serviceName, m.name, p.name));
|
|
1571
|
+
const encodedValueArray = paramSites.length > 0
|
|
1572
|
+
? `[${m.parameters.map((p, index) => `${paramSites[index] ? `encode${paramSites[index].codecId}(${p.name})` : p.name}`).join(", ")}]`
|
|
1573
|
+
: "[]";
|
|
1574
|
+
const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(codecCatalog, serviceName, m.name);
|
|
1372
1575
|
if (m.kind === "Call") {
|
|
1373
1576
|
const ret = mapTypeTextToTs(m.payloadTypeText ?? "void");
|
|
1374
|
-
|
|
1577
|
+
if (payloadSite) {
|
|
1578
|
+
methodLines.push(` async ${m.name}(${args}): Promise<${ret}> { const result = await this._bridge.call<unknown>("${serviceName}", "${m.name}", ${encodedValueArray}); return decode${payloadSite.codecId}(result); }`);
|
|
1579
|
+
}
|
|
1580
|
+
else {
|
|
1581
|
+
methodLines.push(` async ${m.name}(${args}): Promise<${ret}> { return this._bridge.call<${ret}>("${serviceName}", "${m.name}", ${encodedValueArray}); }`);
|
|
1582
|
+
}
|
|
1375
1583
|
continue;
|
|
1376
1584
|
}
|
|
1377
1585
|
if (m.kind === "Emitter") {
|
|
1378
|
-
methodLines.push(` ${m.name}(${args}): void {
|
|
1586
|
+
methodLines.push(` ${m.name}(${args}): void {`);
|
|
1587
|
+
methodLines.push(` let encodedArgs: unknown[];`);
|
|
1588
|
+
methodLines.push(` try {`);
|
|
1589
|
+
methodLines.push(` encodedArgs = ${encodedValueArray};`);
|
|
1590
|
+
methodLines.push(` } catch (error) {`);
|
|
1591
|
+
methodLines.push(` this._bridge.reportFrontendDiagnostic({`);
|
|
1592
|
+
methodLines.push(` code: "SerializationError",`);
|
|
1593
|
+
methodLines.push(` severity: "error",`);
|
|
1594
|
+
methodLines.push(` category: "bridge",`);
|
|
1595
|
+
methodLines.push(` recoverable: true,`);
|
|
1596
|
+
methodLines.push(` message: \`Failed to serialize Emitter ${serviceName}.${m.name}: \${errorMessage(error)}\`,`);
|
|
1597
|
+
methodLines.push(` service: "${serviceName}",`);
|
|
1598
|
+
methodLines.push(` member: "${m.name}",`);
|
|
1599
|
+
methodLines.push(` context: { interaction: "Emitter" }`);
|
|
1600
|
+
methodLines.push(` });`);
|
|
1601
|
+
methodLines.push(` return;`);
|
|
1602
|
+
methodLines.push(` }`);
|
|
1603
|
+
methodLines.push(` this._bridge.emit("${serviceName}", "${m.name}", encodedArgs);`);
|
|
1604
|
+
methodLines.push(` }`);
|
|
1379
1605
|
continue;
|
|
1380
1606
|
}
|
|
1381
1607
|
if (m.kind === "Slot") {
|
|
1382
1608
|
const ret = mapTypeTextToTs(m.payloadTypeText ?? "void");
|
|
1609
|
+
const decodedArgs = m.parameters.map((p, index) => `${paramSites[index] ? `decode${paramSites[index].codecId}(wireArgs[${index}])` : `wireArgs[${index}] as ${mapTypeTextToTs(p.typeText)}`}`).join(", ");
|
|
1383
1610
|
onSlotMembers.push(` ${m.name}: (handler: (${args}) => ${slotHandlerReturnType(ret)}): void => {`);
|
|
1384
|
-
onSlotMembers.push(` this._bridge.registerSlot("${serviceName}", "${m.name}",
|
|
1611
|
+
onSlotMembers.push(` this._bridge.registerSlot("${serviceName}", "${m.name}", (...wireArgs: unknown[]) => {`);
|
|
1612
|
+
onSlotMembers.push(` const result = handler(${decodedArgs});`);
|
|
1613
|
+
if (payloadSite) {
|
|
1614
|
+
onSlotMembers.push(` if (result instanceof Promise) return result.then((value) => value instanceof Error ? value : encode${payloadSite.codecId}(value));`);
|
|
1615
|
+
onSlotMembers.push(` return result instanceof Error ? result : encode${payloadSite.codecId}(result);`);
|
|
1616
|
+
}
|
|
1617
|
+
else {
|
|
1618
|
+
onSlotMembers.push(" return result;");
|
|
1619
|
+
}
|
|
1620
|
+
onSlotMembers.push(" });");
|
|
1385
1621
|
onSlotMembers.push(" },");
|
|
1386
1622
|
continue;
|
|
1387
1623
|
}
|
|
1388
1624
|
if ((m.kind === "Input" || m.kind === "Output") && m.payloadTypeText) {
|
|
1389
1625
|
const tsType = mapTypeTextToTs(m.payloadTypeText);
|
|
1390
|
-
fieldLines.push(` private readonly _${m.name} = signal<${tsType}>(
|
|
1391
|
-
methodLines.push(` ${m.name}(): ${tsType} { return this._${m.name}(); }`);
|
|
1626
|
+
fieldLines.push(` private readonly _${m.name} = signal<${tsType} | undefined>(undefined);`);
|
|
1627
|
+
methodLines.push(` ${m.name}(): ${tsType} | undefined { return this._${m.name}(); }`);
|
|
1392
1628
|
if (m.kind === "Input") {
|
|
1393
1629
|
setMembers.push(` ${m.name}: (value: ${tsType}): void => {`);
|
|
1630
|
+
setMembers.push(` let encodedValue: unknown;`);
|
|
1631
|
+
setMembers.push(` try {`);
|
|
1632
|
+
setMembers.push(` encodedValue = ${payloadSite ? `encode${payloadSite.codecId}(value)` : "value"};`);
|
|
1633
|
+
setMembers.push(` } catch (error) {`);
|
|
1634
|
+
setMembers.push(` this._bridge.reportFrontendDiagnostic({`);
|
|
1635
|
+
setMembers.push(` code: "SerializationError",`);
|
|
1636
|
+
setMembers.push(` severity: "error",`);
|
|
1637
|
+
setMembers.push(` category: "bridge",`);
|
|
1638
|
+
setMembers.push(` recoverable: true,`);
|
|
1639
|
+
setMembers.push(` message: \`Failed to serialize Input ${serviceName}.${m.name}: \${errorMessage(error)}\`,`);
|
|
1640
|
+
setMembers.push(` service: "${serviceName}",`);
|
|
1641
|
+
setMembers.push(` member: "${m.name}",`);
|
|
1642
|
+
setMembers.push(` context: { interaction: "Input" }`);
|
|
1643
|
+
setMembers.push(` });`);
|
|
1644
|
+
setMembers.push(` return;`);
|
|
1645
|
+
setMembers.push(` }`);
|
|
1394
1646
|
setMembers.push(` this._${m.name}.set(value);`);
|
|
1395
|
-
setMembers.push(` this._bridge.setInput("${serviceName}", "${m.name}",
|
|
1647
|
+
setMembers.push(` this._bridge.setInput("${serviceName}", "${m.name}", encodedValue);`);
|
|
1396
1648
|
setMembers.push(" },");
|
|
1397
1649
|
}
|
|
1398
1650
|
if (m.kind === "Output") {
|
|
1399
|
-
constructorBodyLines.push(` this._bridge.onOutput("${serviceName}", "${m.name}", (value) =>
|
|
1651
|
+
constructorBodyLines.push(` this._bridge.onOutput("${serviceName}", "${m.name}", (value) => {`);
|
|
1652
|
+
constructorBodyLines.push(` try {`);
|
|
1653
|
+
constructorBodyLines.push(` this._${m.name}.set(${payloadSite ? `decode${payloadSite.codecId}(value)` : `value as ${tsType}`});`);
|
|
1654
|
+
constructorBodyLines.push(` } catch (error) {`);
|
|
1655
|
+
constructorBodyLines.push(` this._bridge.reportFrontendDiagnostic({`);
|
|
1656
|
+
constructorBodyLines.push(` code: "DeserializationError",`);
|
|
1657
|
+
constructorBodyLines.push(` severity: "error",`);
|
|
1658
|
+
constructorBodyLines.push(` category: "bridge",`);
|
|
1659
|
+
constructorBodyLines.push(` recoverable: true,`);
|
|
1660
|
+
constructorBodyLines.push(` message: \`Failed to deserialize Output ${serviceName}.${m.name}: \${errorMessage(error)}\`,`);
|
|
1661
|
+
constructorBodyLines.push(` service: "${serviceName}",`);
|
|
1662
|
+
constructorBodyLines.push(` member: "${m.name}",`);
|
|
1663
|
+
constructorBodyLines.push(` context: { interaction: "Output" }`);
|
|
1664
|
+
constructorBodyLines.push(` });`);
|
|
1665
|
+
constructorBodyLines.push(` }`);
|
|
1666
|
+
constructorBodyLines.push(` });`);
|
|
1400
1667
|
}
|
|
1401
1668
|
}
|
|
1402
1669
|
if (m.kind === "DropTarget" && m.payloadTypeText) {
|
|
1403
1670
|
const tsType = mapTypeTextToTs(m.payloadTypeText);
|
|
1404
1671
|
fieldLines.push(` private readonly _${m.name} = signal<{ payload: ${tsType}; x: number; y: number } | null>(null);`);
|
|
1405
1672
|
methodLines.push(` ${m.name}(): { payload: ${tsType}; x: number; y: number } | null { return this._${m.name}(); }`);
|
|
1406
|
-
constructorBodyLines.push(` this._bridge.onDrop("${serviceName}", "${m.name}", (payload, x, y) =>
|
|
1673
|
+
constructorBodyLines.push(` this._bridge.onDrop("${serviceName}", "${m.name}", (payload, x, y) => {`);
|
|
1674
|
+
constructorBodyLines.push(` try {`);
|
|
1675
|
+
constructorBodyLines.push(` this._${m.name}.set({ payload: ${payloadSite ? `decode${payloadSite.codecId}(payload)` : `payload as ${tsType}`}, x, y });`);
|
|
1676
|
+
constructorBodyLines.push(` } catch (error) {`);
|
|
1677
|
+
constructorBodyLines.push(` this._bridge.reportFrontendDiagnostic({`);
|
|
1678
|
+
constructorBodyLines.push(` code: "DeserializationError",`);
|
|
1679
|
+
constructorBodyLines.push(` severity: "error",`);
|
|
1680
|
+
constructorBodyLines.push(` category: "bridge",`);
|
|
1681
|
+
constructorBodyLines.push(` recoverable: true,`);
|
|
1682
|
+
constructorBodyLines.push(` message: \`Failed to deserialize DropTarget ${serviceName}.${m.name}: \${errorMessage(error)}\`,`);
|
|
1683
|
+
constructorBodyLines.push(` service: "${serviceName}",`);
|
|
1684
|
+
constructorBodyLines.push(` member: "${m.name}",`);
|
|
1685
|
+
constructorBodyLines.push(` context: { interaction: "DropTarget" }`);
|
|
1686
|
+
constructorBodyLines.push(` });`);
|
|
1687
|
+
constructorBodyLines.push(` }`);
|
|
1688
|
+
constructorBodyLines.push(` });`);
|
|
1407
1689
|
}
|
|
1408
1690
|
if (m.kind === "HoverTarget" && m.payloadTypeText) {
|
|
1409
1691
|
const tsType = mapTypeTextToTs(m.payloadTypeText);
|
|
1410
1692
|
fieldLines.push(` private readonly _${m.name} = signal<{ payload: ${tsType}; x: number; y: number } | null>(null);`);
|
|
1411
1693
|
methodLines.push(` ${m.name}(): { payload: ${tsType}; x: number; y: number } | null { return this._${m.name}(); }`);
|
|
1412
|
-
constructorBodyLines.push(` this._bridge.onHover("${serviceName}", "${m.name}", (payload, x, y) =>
|
|
1694
|
+
constructorBodyLines.push(` this._bridge.onHover("${serviceName}", "${m.name}", (payload, x, y) => {`);
|
|
1695
|
+
constructorBodyLines.push(` try {`);
|
|
1696
|
+
constructorBodyLines.push(` this._${m.name}.set({ payload: ${payloadSite ? `decode${payloadSite.codecId}(payload)` : `payload as ${tsType}`}, x, y });`);
|
|
1697
|
+
constructorBodyLines.push(` } catch (error) {`);
|
|
1698
|
+
constructorBodyLines.push(` this._bridge.reportFrontendDiagnostic({`);
|
|
1699
|
+
constructorBodyLines.push(` code: "DeserializationError",`);
|
|
1700
|
+
constructorBodyLines.push(` severity: "error",`);
|
|
1701
|
+
constructorBodyLines.push(` category: "bridge",`);
|
|
1702
|
+
constructorBodyLines.push(` recoverable: true,`);
|
|
1703
|
+
constructorBodyLines.push(` message: \`Failed to deserialize HoverTarget ${serviceName}.${m.name}: \${errorMessage(error)}\`,`);
|
|
1704
|
+
constructorBodyLines.push(` service: "${serviceName}",`);
|
|
1705
|
+
constructorBodyLines.push(` member: "${m.name}",`);
|
|
1706
|
+
constructorBodyLines.push(` context: { interaction: "HoverTarget" }`);
|
|
1707
|
+
constructorBodyLines.push(` });`);
|
|
1708
|
+
constructorBodyLines.push(` }`);
|
|
1709
|
+
constructorBodyLines.push(` });`);
|
|
1413
1710
|
constructorBodyLines.push(` this._bridge.onHoverLeft("${serviceName}", "${m.name}", () => this._${m.name}.set(null));`);
|
|
1414
1711
|
}
|
|
1415
1712
|
}
|
|
@@ -1458,7 +1755,7 @@ function renderTsServiceDts(spec, serviceName) {
|
|
|
1458
1755
|
}
|
|
1459
1756
|
if ((m.kind === "Input" || m.kind === "Output") && m.payloadTypeText) {
|
|
1460
1757
|
const tsType = mapTypeTextToTs(m.payloadTypeText);
|
|
1461
|
-
classMembers.push(` ${m.name}(): ${tsType};`);
|
|
1758
|
+
classMembers.push(` ${m.name}(): ${tsType} | undefined;`);
|
|
1462
1759
|
if (m.kind === "Input") {
|
|
1463
1760
|
setMembers.push(` ${m.name}(value: ${tsType}): void;`);
|
|
1464
1761
|
}
|
|
@@ -1489,7 +1786,8 @@ export declare class ${serviceName} {
|
|
|
1489
1786
|
}`;
|
|
1490
1787
|
}
|
|
1491
1788
|
function renderTsServices(spec) {
|
|
1492
|
-
const
|
|
1789
|
+
const codecCatalog = (0, structured_top_level_codecs_1.buildStructuredCodecCatalog)(spec);
|
|
1790
|
+
const serviceClasses = spec.services.map((s) => renderTsService(spec, s.name, codecCatalog)).join("\n");
|
|
1493
1791
|
const externalTypeImports = renderRequiredTypeImports(spec, `frontend/${(0, layout_1.generatedFrontendDirName)(spec.widgetName)}/services.ts`).trim();
|
|
1494
1792
|
const localTypeImports = renderLocalTypeImports(spec).trim();
|
|
1495
1793
|
const typeImports = [externalTypeImports, localTypeImports].filter((s) => s.length > 0).join("\n");
|
|
@@ -1497,6 +1795,9 @@ function renderTsServices(spec) {
|
|
|
1497
1795
|
return `import { Injectable, inject, signal } from "@angular/core";
|
|
1498
1796
|
${typeImportsBlock}
|
|
1499
1797
|
|
|
1798
|
+
// Structured/top-level codec helpers
|
|
1799
|
+
${(0, structured_top_level_codecs_1.renderTsStructuredCodecHelpers)(codecCatalog)}
|
|
1800
|
+
|
|
1500
1801
|
type SlotHandler = (...args: unknown[]) => unknown;
|
|
1501
1802
|
type OutputHandler = (value: unknown) => void;
|
|
1502
1803
|
type SlotInvocationListener = (requestId: string, service: string, member: string, args: unknown[]) => void;
|
|
@@ -1504,6 +1805,28 @@ type OutputListener = (service: string, member: string, value: unknown) => void;
|
|
|
1504
1805
|
type DropListener = (service: string, member: string, payload: unknown, x: number, y: number) => void;
|
|
1505
1806
|
type HoverListener = (service: string, member: string, payload: unknown, x: number, y: number) => void;
|
|
1506
1807
|
type HoverLeftListener = (service: string, member: string) => void;
|
|
1808
|
+
type HostDiagnosticListener = (payload: unknown) => void;
|
|
1809
|
+
type DisconnectListener = () => void;
|
|
1810
|
+
|
|
1811
|
+
export type AnQstBridgeSeverity = "info" | "warn" | "error" | "fatal";
|
|
1812
|
+
export type AnQstBridgeSource = "frontend" | "host";
|
|
1813
|
+
export type AnQstBridgeTransport = "qt-webchannel" | "dev-websocket";
|
|
1814
|
+
export type AnQstBridgeState = "starting" | "ready" | "failed" | "disconnected";
|
|
1815
|
+
|
|
1816
|
+
export interface AnQstBridgeDiagnostic {
|
|
1817
|
+
code: string;
|
|
1818
|
+
severity: AnQstBridgeSeverity;
|
|
1819
|
+
category: string;
|
|
1820
|
+
recoverable: boolean;
|
|
1821
|
+
message: string;
|
|
1822
|
+
timestamp: string;
|
|
1823
|
+
source: AnQstBridgeSource;
|
|
1824
|
+
transport?: AnQstBridgeTransport;
|
|
1825
|
+
service?: string;
|
|
1826
|
+
member?: string;
|
|
1827
|
+
requestId?: string;
|
|
1828
|
+
context?: Record<string, unknown>;
|
|
1829
|
+
}
|
|
1507
1830
|
|
|
1508
1831
|
interface HostBridgeApi {
|
|
1509
1832
|
anQstBridge_call(service: string, member: string, args: unknown[], callback: (result: unknown) => void): void;
|
|
@@ -1515,6 +1838,7 @@ interface HostBridgeApi {
|
|
|
1515
1838
|
anQstBridge_slotInvocationRequested: {
|
|
1516
1839
|
connect: (cb: (requestId: string, service: string, member: string, args: unknown[]) => void) => void;
|
|
1517
1840
|
};
|
|
1841
|
+
anQstBridge_hostDiagnostic?: { connect: (cb: (payload: unknown) => void) => void };
|
|
1518
1842
|
anQstBridge_dropReceived: { connect: (cb: (service: string, member: string, payload: unknown, x: number, y: number) => void) => void };
|
|
1519
1843
|
anQstBridge_hoverUpdated: { connect: (cb: (service: string, member: string, payload: unknown, x: number, y: number) => void) => void };
|
|
1520
1844
|
anQstBridge_hoverLeft: { connect: (cb: (service: string, member: string) => void) => void };
|
|
@@ -1528,6 +1852,7 @@ interface QWebChannelCtor {
|
|
|
1528
1852
|
}
|
|
1529
1853
|
|
|
1530
1854
|
interface BridgeAdapter {
|
|
1855
|
+
readonly transport: AnQstBridgeTransport;
|
|
1531
1856
|
call<T>(service: string, member: string, args: unknown[]): Promise<T>;
|
|
1532
1857
|
emit(service: string, member: string, args: unknown[]): void;
|
|
1533
1858
|
setInput(service: string, member: string, value: unknown): void;
|
|
@@ -1535,11 +1860,82 @@ interface BridgeAdapter {
|
|
|
1535
1860
|
resolveSlot(requestId: string, ok: boolean, payload: unknown, error: string): void;
|
|
1536
1861
|
onOutput(handler: OutputListener): void;
|
|
1537
1862
|
onSlotInvocation(handler: SlotInvocationListener): void;
|
|
1863
|
+
onHostDiagnostic(handler: HostDiagnosticListener): void;
|
|
1864
|
+
onDisconnected(handler: DisconnectListener): void;
|
|
1538
1865
|
onDrop(handler: DropListener): void;
|
|
1539
1866
|
onHover(handler: HoverListener): void;
|
|
1540
1867
|
onHoverLeft(handler: HoverLeftListener): void;
|
|
1541
1868
|
}
|
|
1542
1869
|
|
|
1870
|
+
function errorMessage(error: unknown): string {
|
|
1871
|
+
if (error instanceof Error && typeof error.message === "string" && error.message.length > 0) {
|
|
1872
|
+
return error.message;
|
|
1873
|
+
}
|
|
1874
|
+
return String(error);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
function normalizeSeverity(value: unknown): AnQstBridgeSeverity {
|
|
1878
|
+
if (value === "info" || value === "warn" || value === "error" || value === "fatal") {
|
|
1879
|
+
return value;
|
|
1880
|
+
}
|
|
1881
|
+
return "error";
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
1885
|
+
if (value === null || typeof value !== "object") {
|
|
1886
|
+
return undefined;
|
|
1887
|
+
}
|
|
1888
|
+
return value as Record<string, unknown>;
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
function readString(record: Record<string, unknown> | undefined, key: string): string | undefined {
|
|
1892
|
+
const value = record?.[key];
|
|
1893
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
function readBoolean(record: Record<string, unknown> | undefined, key: string): boolean | undefined {
|
|
1897
|
+
const value = record?.[key];
|
|
1898
|
+
return typeof value === "boolean" ? value : undefined;
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
function readContext(record: Record<string, unknown> | undefined): Record<string, unknown> | undefined {
|
|
1902
|
+
const context = asRecord(record?.["context"]);
|
|
1903
|
+
return context === undefined ? undefined : context;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
function normalizeHostDiagnostic(
|
|
1907
|
+
payload: unknown,
|
|
1908
|
+
transport: AnQstBridgeTransport
|
|
1909
|
+
): Omit<AnQstBridgeDiagnostic, "timestamp"> {
|
|
1910
|
+
const row = asRecord(payload);
|
|
1911
|
+
if (row === undefined) {
|
|
1912
|
+
return {
|
|
1913
|
+
code: "HostDiagnosticMalformed",
|
|
1914
|
+
severity: "error",
|
|
1915
|
+
category: "bridge",
|
|
1916
|
+
recoverable: true,
|
|
1917
|
+
message: "Host emitted a malformed diagnostic payload.",
|
|
1918
|
+
source: "host",
|
|
1919
|
+
transport
|
|
1920
|
+
};
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
const context = readContext(row);
|
|
1924
|
+
return {
|
|
1925
|
+
code: readString(row, "code") ?? "HostDiagnostic",
|
|
1926
|
+
severity: normalizeSeverity(row["severity"]),
|
|
1927
|
+
category: readString(row, "category") ?? "bridge",
|
|
1928
|
+
recoverable: readBoolean(row, "recoverable") ?? true,
|
|
1929
|
+
message: readString(row, "message") ?? "Host emitted a diagnostic payload.",
|
|
1930
|
+
source: "host",
|
|
1931
|
+
transport,
|
|
1932
|
+
service: readString(row, "service") ?? readString(context, "service"),
|
|
1933
|
+
member: readString(row, "member") ?? readString(context, "member"),
|
|
1934
|
+
requestId: readString(row, "requestId") ?? readString(context, "requestId"),
|
|
1935
|
+
context
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1543
1939
|
function isBridgeCallError(value: unknown): value is {
|
|
1544
1940
|
code: unknown;
|
|
1545
1941
|
message: unknown;
|
|
@@ -1559,6 +1955,8 @@ function isBridgeCallError(value: unknown): value is {
|
|
|
1559
1955
|
}
|
|
1560
1956
|
|
|
1561
1957
|
class QtWebChannelAdapter implements BridgeAdapter {
|
|
1958
|
+
readonly transport = "qt-webchannel" as const;
|
|
1959
|
+
|
|
1562
1960
|
private constructor(private readonly host: HostBridgeApi) {}
|
|
1563
1961
|
|
|
1564
1962
|
static async create(): Promise<QtWebChannelAdapter> {
|
|
@@ -1626,6 +2024,14 @@ class QtWebChannelAdapter implements BridgeAdapter {
|
|
|
1626
2024
|
this.host.anQstBridge_slotInvocationRequested.connect(handler);
|
|
1627
2025
|
}
|
|
1628
2026
|
|
|
2027
|
+
onHostDiagnostic(handler: HostDiagnosticListener): void {
|
|
2028
|
+
this.host.anQstBridge_hostDiagnostic?.connect(handler);
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
onDisconnected(_handler: DisconnectListener): void {
|
|
2032
|
+
// QWebChannel does not expose a deterministic disconnect event here.
|
|
2033
|
+
}
|
|
2034
|
+
|
|
1629
2035
|
onDrop(handler: DropListener): void {
|
|
1630
2036
|
this.host.anQstBridge_dropReceived.connect(handler);
|
|
1631
2037
|
}
|
|
@@ -1640,6 +2046,7 @@ class QtWebChannelAdapter implements BridgeAdapter {
|
|
|
1640
2046
|
}
|
|
1641
2047
|
|
|
1642
2048
|
class WebSocketBridgeAdapter implements BridgeAdapter {
|
|
2049
|
+
readonly transport = "dev-websocket" as const;
|
|
1643
2050
|
private readonly pending = new Map<string, {
|
|
1644
2051
|
service: string;
|
|
1645
2052
|
member: string;
|
|
@@ -1649,6 +2056,8 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
|
|
|
1649
2056
|
}>();
|
|
1650
2057
|
private readonly outputListeners: OutputListener[] = [];
|
|
1651
2058
|
private readonly slotListeners: SlotInvocationListener[] = [];
|
|
2059
|
+
private readonly hostDiagnosticListeners: HostDiagnosticListener[] = [];
|
|
2060
|
+
private readonly disconnectListeners: DisconnectListener[] = [];
|
|
1652
2061
|
private readonly dropListeners: DropListener[] = [];
|
|
1653
2062
|
private readonly hoverListeners: HoverListener[] = [];
|
|
1654
2063
|
private readonly hoverLeftListeners: HoverLeftListener[] = [];
|
|
@@ -1720,7 +2129,9 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
|
|
|
1720
2129
|
return;
|
|
1721
2130
|
}
|
|
1722
2131
|
if (type === "hostError") {
|
|
1723
|
-
|
|
2132
|
+
for (const listener of this.hostDiagnosticListeners) {
|
|
2133
|
+
listener(message["payload"]);
|
|
2134
|
+
}
|
|
1724
2135
|
return;
|
|
1725
2136
|
}
|
|
1726
2137
|
if (type === "widgetReattached") {
|
|
@@ -1739,6 +2150,9 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
|
|
|
1739
2150
|
});
|
|
1740
2151
|
}
|
|
1741
2152
|
this.pending.clear();
|
|
2153
|
+
for (const listener of this.disconnectListeners) {
|
|
2154
|
+
listener();
|
|
2155
|
+
}
|
|
1742
2156
|
});
|
|
1743
2157
|
}
|
|
1744
2158
|
|
|
@@ -1807,6 +2221,14 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
|
|
|
1807
2221
|
this.slotListeners.push(handler);
|
|
1808
2222
|
}
|
|
1809
2223
|
|
|
2224
|
+
onHostDiagnostic(handler: HostDiagnosticListener): void {
|
|
2225
|
+
this.hostDiagnosticListeners.push(handler);
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
onDisconnected(handler: DisconnectListener): void {
|
|
2229
|
+
this.disconnectListeners.push(handler);
|
|
2230
|
+
}
|
|
2231
|
+
|
|
1810
2232
|
onDrop(handler: DropListener): void {
|
|
1811
2233
|
this.dropListeners.push(handler);
|
|
1812
2234
|
}
|
|
@@ -1822,53 +2244,116 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
|
|
|
1822
2244
|
|
|
1823
2245
|
@Injectable({ providedIn: "root" })
|
|
1824
2246
|
class AnQstBridgeRuntime {
|
|
2247
|
+
private static readonly maxDiagnostics = 50;
|
|
1825
2248
|
private adapter: BridgeAdapter | null = null;
|
|
1826
2249
|
private readonly slotHandlers = new Map<string, SlotHandler>();
|
|
1827
2250
|
private readonly outputHandlers = new Map<string, OutputHandler[]>();
|
|
1828
2251
|
private readonly dropHandlers = new Map<string, ((payload: unknown, x: number, y: number) => void)[]>();
|
|
1829
2252
|
private readonly hoverHandlers = new Map<string, ((payload: unknown, x: number, y: number) => void)[]>();
|
|
1830
2253
|
private readonly hoverLeftHandlers = new Map<string, (() => void)[]>();
|
|
1831
|
-
private readonly
|
|
2254
|
+
private readonly diagnosticListeners = new Set<(diagnostic: AnQstBridgeDiagnostic) => void>();
|
|
2255
|
+
private readonly _diagnostics = signal<readonly AnQstBridgeDiagnostic[]>([]);
|
|
2256
|
+
private readonly _state = signal<AnQstBridgeState>("starting");
|
|
2257
|
+
private readonly startup = this.init().catch((error) => {
|
|
2258
|
+
this._state.set("failed");
|
|
2259
|
+
this.reportFrontendDiagnostic({
|
|
2260
|
+
code: "BridgeBootstrapError",
|
|
2261
|
+
severity: "fatal",
|
|
2262
|
+
category: "bridge",
|
|
2263
|
+
recoverable: false,
|
|
2264
|
+
message: \`Failed to initialize bridge: \${errorMessage(error)}\`
|
|
2265
|
+
});
|
|
2266
|
+
throw error;
|
|
2267
|
+
});
|
|
2268
|
+
|
|
2269
|
+
diagnostics(): readonly AnQstBridgeDiagnostic[] {
|
|
2270
|
+
return this._diagnostics();
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
state(): AnQstBridgeState {
|
|
2274
|
+
return this._state();
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
subscribeDiagnostics(listener: (diagnostic: AnQstBridgeDiagnostic) => void): () => void {
|
|
2278
|
+
this.diagnosticListeners.add(listener);
|
|
2279
|
+
return () => this.diagnosticListeners.delete(listener);
|
|
2280
|
+
}
|
|
1832
2281
|
|
|
1833
2282
|
async ready(): Promise<void> {
|
|
1834
2283
|
return this.startup;
|
|
1835
2284
|
}
|
|
1836
2285
|
|
|
2286
|
+
reportFrontendDiagnostic(diagnostic: Omit<AnQstBridgeDiagnostic, "timestamp" | "source">): void {
|
|
2287
|
+
this.pushDiagnostic({
|
|
2288
|
+
...diagnostic,
|
|
2289
|
+
source: "frontend",
|
|
2290
|
+
transport: diagnostic.transport ?? this.adapter?.transport,
|
|
2291
|
+
timestamp: new Date().toISOString()
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2294
|
+
|
|
1837
2295
|
async call<T>(service: string, member: string, args: unknown[]): Promise<T> {
|
|
1838
2296
|
const adapter = await this.requireAdapter();
|
|
1839
2297
|
return adapter.call<T>(service, member, args);
|
|
1840
2298
|
}
|
|
1841
2299
|
|
|
1842
2300
|
emit(service: string, member: string, args: unknown[]): void {
|
|
1843
|
-
|
|
1844
|
-
this.adapter.emit(service, member, args);
|
|
1845
|
-
return;
|
|
1846
|
-
}
|
|
1847
|
-
this.ready()
|
|
1848
|
-
.then(() => this.requireAdapterSync().emit(service, member, args))
|
|
1849
|
-
.catch((error) => console.error(error));
|
|
2301
|
+
this.publishNonCall("Emitter", service, member, (adapter) => adapter.emit(service, member, args));
|
|
1850
2302
|
}
|
|
1851
2303
|
|
|
1852
2304
|
setInput(service: string, member: string, value: unknown): void {
|
|
1853
|
-
|
|
1854
|
-
this.adapter.setInput(service, member, value);
|
|
1855
|
-
return;
|
|
1856
|
-
}
|
|
1857
|
-
this.ready()
|
|
1858
|
-
.then(() => this.requireAdapterSync().setInput(service, member, value))
|
|
1859
|
-
.catch((error) => console.error(error));
|
|
2305
|
+
this.publishNonCall("Input", service, member, (adapter) => adapter.setInput(service, member, value));
|
|
1860
2306
|
}
|
|
1861
2307
|
|
|
1862
2308
|
registerSlot(service: string, member: string, handler: SlotHandler): void {
|
|
1863
2309
|
const key = this.key(service, member);
|
|
1864
2310
|
this.slotHandlers.set(key, handler);
|
|
1865
2311
|
if (this.adapter !== null) {
|
|
1866
|
-
|
|
2312
|
+
try {
|
|
2313
|
+
this.adapter.registerSlot(service, member);
|
|
2314
|
+
} catch (error) {
|
|
2315
|
+
this.reportFrontendDiagnostic({
|
|
2316
|
+
code: "BridgePublishError",
|
|
2317
|
+
severity: "error",
|
|
2318
|
+
category: "bridge",
|
|
2319
|
+
recoverable: true,
|
|
2320
|
+
message: \`Failed to register Slot \${service}.\${member}: \${errorMessage(error)}\`,
|
|
2321
|
+
service,
|
|
2322
|
+
member,
|
|
2323
|
+
context: { interaction: "Slot" }
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
1867
2326
|
return;
|
|
1868
2327
|
}
|
|
1869
2328
|
this.ready()
|
|
1870
|
-
.then(() =>
|
|
1871
|
-
|
|
2329
|
+
.then(() => {
|
|
2330
|
+
try {
|
|
2331
|
+
this.requireAdapterSync().registerSlot(service, member);
|
|
2332
|
+
} catch (error) {
|
|
2333
|
+
this.reportFrontendDiagnostic({
|
|
2334
|
+
code: "BridgePublishError",
|
|
2335
|
+
severity: "error",
|
|
2336
|
+
category: "bridge",
|
|
2337
|
+
recoverable: true,
|
|
2338
|
+
message: \`Failed to register Slot \${service}.\${member}: \${errorMessage(error)}\`,
|
|
2339
|
+
service,
|
|
2340
|
+
member,
|
|
2341
|
+
context: { interaction: "Slot" }
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
})
|
|
2345
|
+
.catch((error) => {
|
|
2346
|
+
this.reportFrontendDiagnostic({
|
|
2347
|
+
code: "BridgePublishError",
|
|
2348
|
+
severity: "error",
|
|
2349
|
+
category: "bridge",
|
|
2350
|
+
recoverable: true,
|
|
2351
|
+
message: \`Failed to register Slot \${service}.\${member}: \${errorMessage(error)}\`,
|
|
2352
|
+
service,
|
|
2353
|
+
member,
|
|
2354
|
+
context: { interaction: "Slot" }
|
|
2355
|
+
});
|
|
2356
|
+
});
|
|
1872
2357
|
}
|
|
1873
2358
|
|
|
1874
2359
|
onOutput(service: string, member: string, handler: OutputHandler): void {
|
|
@@ -1911,6 +2396,73 @@ class AnQstBridgeRuntime {
|
|
|
1911
2396
|
return this.requireAdapterSync();
|
|
1912
2397
|
}
|
|
1913
2398
|
|
|
2399
|
+
private pushDiagnostic(diagnostic: AnQstBridgeDiagnostic): void {
|
|
2400
|
+
const previous = this._diagnostics();
|
|
2401
|
+
const trimmed = previous.length >= AnQstBridgeRuntime.maxDiagnostics
|
|
2402
|
+
? previous.slice(previous.length - (AnQstBridgeRuntime.maxDiagnostics - 1))
|
|
2403
|
+
: previous;
|
|
2404
|
+
const next = [...trimmed, diagnostic];
|
|
2405
|
+
this._diagnostics.set(next);
|
|
2406
|
+
for (const listener of this.diagnosticListeners) {
|
|
2407
|
+
listener(diagnostic);
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
private publishNonCall(
|
|
2412
|
+
interaction: "Emitter" | "Input",
|
|
2413
|
+
service: string,
|
|
2414
|
+
member: string,
|
|
2415
|
+
publish: (adapter: BridgeAdapter) => void
|
|
2416
|
+
): void {
|
|
2417
|
+
if (this.adapter !== null) {
|
|
2418
|
+
try {
|
|
2419
|
+
publish(this.adapter);
|
|
2420
|
+
} catch (error) {
|
|
2421
|
+
this.reportFrontendDiagnostic({
|
|
2422
|
+
code: "BridgePublishError",
|
|
2423
|
+
severity: "error",
|
|
2424
|
+
category: "bridge",
|
|
2425
|
+
recoverable: true,
|
|
2426
|
+
message: \`Failed to publish \${interaction} \${service}.\${member}: \${errorMessage(error)}\`,
|
|
2427
|
+
service,
|
|
2428
|
+
member,
|
|
2429
|
+
context: { interaction }
|
|
2430
|
+
});
|
|
2431
|
+
}
|
|
2432
|
+
return;
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
this.ready()
|
|
2436
|
+
.then(() => {
|
|
2437
|
+
try {
|
|
2438
|
+
publish(this.requireAdapterSync());
|
|
2439
|
+
} catch (error) {
|
|
2440
|
+
this.reportFrontendDiagnostic({
|
|
2441
|
+
code: "BridgePublishError",
|
|
2442
|
+
severity: "error",
|
|
2443
|
+
category: "bridge",
|
|
2444
|
+
recoverable: true,
|
|
2445
|
+
message: \`Failed to publish \${interaction} \${service}.\${member}: \${errorMessage(error)}\`,
|
|
2446
|
+
service,
|
|
2447
|
+
member,
|
|
2448
|
+
context: { interaction }
|
|
2449
|
+
});
|
|
2450
|
+
}
|
|
2451
|
+
})
|
|
2452
|
+
.catch((error) => {
|
|
2453
|
+
this.reportFrontendDiagnostic({
|
|
2454
|
+
code: "BridgePublishError",
|
|
2455
|
+
severity: "error",
|
|
2456
|
+
category: "bridge",
|
|
2457
|
+
recoverable: true,
|
|
2458
|
+
message: \`Failed to publish \${interaction} \${service}.\${member}: \${errorMessage(error)}\`,
|
|
2459
|
+
service,
|
|
2460
|
+
member,
|
|
2461
|
+
context: { interaction }
|
|
2462
|
+
});
|
|
2463
|
+
});
|
|
2464
|
+
}
|
|
2465
|
+
|
|
1914
2466
|
private async init(): Promise<void> {
|
|
1915
2467
|
const anyWindow = window as unknown as { qt?: { webChannelTransport?: unknown }; QWebChannel?: QWebChannelCtor };
|
|
1916
2468
|
if (typeof anyWindow.QWebChannel === "function" && anyWindow.qt?.webChannelTransport !== undefined) {
|
|
@@ -1919,44 +2471,98 @@ class AnQstBridgeRuntime {
|
|
|
1919
2471
|
this.adapter = await WebSocketBridgeAdapter.create();
|
|
1920
2472
|
}
|
|
1921
2473
|
|
|
1922
|
-
this.adapter
|
|
2474
|
+
const adapter = this.adapter;
|
|
2475
|
+
adapter.onHostDiagnostic((payload) => {
|
|
2476
|
+
this.pushDiagnostic({
|
|
2477
|
+
...normalizeHostDiagnostic(payload, adapter.transport),
|
|
2478
|
+
timestamp: new Date().toISOString()
|
|
2479
|
+
});
|
|
2480
|
+
});
|
|
2481
|
+
adapter.onDisconnected(() => {
|
|
2482
|
+
this._state.set("disconnected");
|
|
2483
|
+
this.reportFrontendDiagnostic({
|
|
2484
|
+
code: "BridgeDisconnectedError",
|
|
2485
|
+
severity: "error",
|
|
2486
|
+
category: "bridge",
|
|
2487
|
+
recoverable: true,
|
|
2488
|
+
message: "Bridge disconnected.",
|
|
2489
|
+
transport: adapter.transport
|
|
2490
|
+
});
|
|
2491
|
+
});
|
|
2492
|
+
|
|
2493
|
+
adapter.onOutput((service, member, value) => {
|
|
1923
2494
|
const key = this.key(service, member);
|
|
1924
2495
|
for (const outputHandler of this.outputHandlers.get(key) ?? []) {
|
|
1925
2496
|
outputHandler(value);
|
|
1926
2497
|
}
|
|
1927
2498
|
});
|
|
1928
|
-
|
|
2499
|
+
adapter.onSlotInvocation(async (requestId, service, member, args) => {
|
|
1929
2500
|
const key = this.key(service, member);
|
|
1930
2501
|
const handler = this.slotHandlers.get(key);
|
|
1931
2502
|
if (handler === undefined) {
|
|
1932
|
-
this.
|
|
2503
|
+
this.reportFrontendDiagnostic({
|
|
2504
|
+
code: "HandlerNotRegisteredError",
|
|
2505
|
+
severity: "error",
|
|
2506
|
+
category: "bridge",
|
|
2507
|
+
recoverable: true,
|
|
2508
|
+
message: \`No slot handler registered for \${service}.\${member}.\`,
|
|
2509
|
+
service,
|
|
2510
|
+
member,
|
|
2511
|
+
requestId,
|
|
2512
|
+
context: { interaction: "Slot" }
|
|
2513
|
+
});
|
|
2514
|
+
adapter.resolveSlot(requestId, false, undefined, "No slot handler registered.");
|
|
1933
2515
|
return;
|
|
1934
2516
|
}
|
|
1935
2517
|
try {
|
|
1936
2518
|
const result = await Promise.resolve(handler(...args));
|
|
1937
2519
|
if (result instanceof Error) {
|
|
1938
|
-
this.
|
|
2520
|
+
this.reportFrontendDiagnostic({
|
|
2521
|
+
code: "SlotRequestFailed",
|
|
2522
|
+
severity: "error",
|
|
2523
|
+
category: "bridge",
|
|
2524
|
+
recoverable: true,
|
|
2525
|
+
message: result.message.length > 0
|
|
2526
|
+
? result.message
|
|
2527
|
+
: \`Slot \${service}.\${member} returned an Error.\`,
|
|
2528
|
+
service,
|
|
2529
|
+
member,
|
|
2530
|
+
requestId,
|
|
2531
|
+
context: { interaction: "Slot" }
|
|
2532
|
+
});
|
|
2533
|
+
adapter.resolveSlot(requestId, false, undefined, result.message);
|
|
1939
2534
|
return;
|
|
1940
2535
|
}
|
|
1941
|
-
|
|
2536
|
+
adapter.resolveSlot(requestId, true, result, "");
|
|
1942
2537
|
} catch (error) {
|
|
1943
|
-
const message =
|
|
1944
|
-
this.
|
|
2538
|
+
const message = errorMessage(error);
|
|
2539
|
+
this.reportFrontendDiagnostic({
|
|
2540
|
+
code: "SlotHandlerError",
|
|
2541
|
+
severity: "error",
|
|
2542
|
+
category: "bridge",
|
|
2543
|
+
recoverable: true,
|
|
2544
|
+
message: \`Slot handler \${service}.\${member} threw: \${message}\`,
|
|
2545
|
+
service,
|
|
2546
|
+
member,
|
|
2547
|
+
requestId,
|
|
2548
|
+
context: { interaction: "Slot" }
|
|
2549
|
+
});
|
|
2550
|
+
adapter.resolveSlot(requestId, false, undefined, message);
|
|
1945
2551
|
}
|
|
1946
2552
|
});
|
|
1947
|
-
|
|
2553
|
+
adapter.onDrop((service, member, payload, x, y) => {
|
|
1948
2554
|
const key = this.key(service, member);
|
|
1949
2555
|
for (const handler of this.dropHandlers.get(key) ?? []) {
|
|
1950
2556
|
handler(payload, x, y);
|
|
1951
2557
|
}
|
|
1952
2558
|
});
|
|
1953
|
-
|
|
2559
|
+
adapter.onHover((service, member, payload, x, y) => {
|
|
1954
2560
|
const key = this.key(service, member);
|
|
1955
2561
|
for (const handler of this.hoverHandlers.get(key) ?? []) {
|
|
1956
2562
|
handler(payload, x, y);
|
|
1957
2563
|
}
|
|
1958
2564
|
});
|
|
1959
|
-
|
|
2565
|
+
adapter.onHoverLeft((service, member) => {
|
|
1960
2566
|
const key = this.key(service, member);
|
|
1961
2567
|
for (const handler of this.hoverLeftHandlers.get(key) ?? []) {
|
|
1962
2568
|
handler();
|
|
@@ -1965,9 +2571,10 @@ class AnQstBridgeRuntime {
|
|
|
1965
2571
|
for (const key of this.slotHandlers.keys()) {
|
|
1966
2572
|
const parts = key.split("::");
|
|
1967
2573
|
if (parts.length === 2) {
|
|
1968
|
-
|
|
2574
|
+
adapter.registerSlot(parts[0], parts[1]);
|
|
1969
2575
|
}
|
|
1970
2576
|
}
|
|
2577
|
+
this._state.set("ready");
|
|
1971
2578
|
}
|
|
1972
2579
|
|
|
1973
2580
|
private key(service: string, member: string): string {
|
|
@@ -1975,6 +2582,23 @@ class AnQstBridgeRuntime {
|
|
|
1975
2582
|
}
|
|
1976
2583
|
|
|
1977
2584
|
}
|
|
2585
|
+
|
|
2586
|
+
@Injectable({ providedIn: "root" })
|
|
2587
|
+
export class AnQstBridgeDiagnostics {
|
|
2588
|
+
private readonly _bridge = inject(AnQstBridgeRuntime);
|
|
2589
|
+
|
|
2590
|
+
diagnostics(): readonly AnQstBridgeDiagnostic[] {
|
|
2591
|
+
return this._bridge.diagnostics();
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
state(): AnQstBridgeState {
|
|
2595
|
+
return this._bridge.state();
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
subscribe(listener: (diagnostic: AnQstBridgeDiagnostic) => void): () => void {
|
|
2599
|
+
return this._bridge.subscribeDiagnostics(listener);
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
1978
2602
|
${serviceClasses}
|
|
1979
2603
|
`;
|
|
1980
2604
|
}
|
|
@@ -1987,10 +2611,38 @@ function renderTsTypes(spec) {
|
|
|
1987
2611
|
function renderTypeServicesDts(spec) {
|
|
1988
2612
|
const externalTypeImports = renderRequiredTypeImports(spec, `frontend/${(0, layout_1.generatedFrontendDirName)(spec.widgetName)}/types/services.d.ts`).trim();
|
|
1989
2613
|
const localTypeImports = renderLocalTypeImports(spec).trim();
|
|
2614
|
+
const bridgeDiagnosticsDecl = `export type AnQstBridgeSeverity = "info" | "warn" | "error" | "fatal";
|
|
2615
|
+
|
|
2616
|
+
export type AnQstBridgeSource = "frontend" | "host";
|
|
2617
|
+
|
|
2618
|
+
export type AnQstBridgeTransport = "qt-webchannel" | "dev-websocket";
|
|
2619
|
+
|
|
2620
|
+
export type AnQstBridgeState = "starting" | "ready" | "failed" | "disconnected";
|
|
2621
|
+
|
|
2622
|
+
export interface AnQstBridgeDiagnostic {
|
|
2623
|
+
code: string;
|
|
2624
|
+
severity: AnQstBridgeSeverity;
|
|
2625
|
+
category: string;
|
|
2626
|
+
recoverable: boolean;
|
|
2627
|
+
message: string;
|
|
2628
|
+
timestamp: string;
|
|
2629
|
+
source: AnQstBridgeSource;
|
|
2630
|
+
transport?: AnQstBridgeTransport;
|
|
2631
|
+
service?: string;
|
|
2632
|
+
member?: string;
|
|
2633
|
+
requestId?: string;
|
|
2634
|
+
context?: Record<string, unknown>;
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
export declare class AnQstBridgeDiagnostics {
|
|
2638
|
+
diagnostics(): readonly AnQstBridgeDiagnostic[];
|
|
2639
|
+
state(): AnQstBridgeState;
|
|
2640
|
+
subscribe(listener: (diagnostic: AnQstBridgeDiagnostic) => void): () => void;
|
|
2641
|
+
}`;
|
|
1990
2642
|
const serviceDecls = spec.services
|
|
1991
2643
|
.map((s) => renderTsServiceDts(spec, s.name))
|
|
1992
2644
|
.join("\n\n");
|
|
1993
|
-
const sections = [externalTypeImports, localTypeImports, serviceDecls.trim()].filter((s) => s.length > 0);
|
|
2645
|
+
const sections = [externalTypeImports, localTypeImports, bridgeDiagnosticsDecl, serviceDecls.trim()].filter((s) => s.length > 0);
|
|
1994
2646
|
return sections.length > 0 ? `${sections.join("\n\n")}\n` : "";
|
|
1995
2647
|
}
|
|
1996
2648
|
function renderTypeTypesDts(spec) {
|
|
@@ -2066,6 +2718,7 @@ function renderNodeExpressWsTypes(spec) {
|
|
|
2066
2718
|
return sections.length > 0 ? `${sections.join("\n\n")}\n` : "";
|
|
2067
2719
|
}
|
|
2068
2720
|
function renderNodeExpressWsIndex(spec) {
|
|
2721
|
+
const codecCatalog = (0, structured_top_level_codecs_1.buildStructuredCodecCatalog)(spec);
|
|
2069
2722
|
const typeImports = renderRequiredTypeImports(spec, `backend/node/express/${generatedNodeExpressWsDirName(spec.widgetName)}/index.ts`);
|
|
2070
2723
|
const typeDecls = renderTypeDeclarations(spec, true);
|
|
2071
2724
|
const handlerBridgeTypeName = `${spec.widgetName}HandlerBridge`;
|
|
@@ -2097,8 +2750,13 @@ function renderNodeExpressWsIndex(spec) {
|
|
|
2097
2750
|
.map((member) => {
|
|
2098
2751
|
const ret = mapTypeTextToTs(member.payloadTypeText ?? "void");
|
|
2099
2752
|
const args = nodeParamArgs(member);
|
|
2753
|
+
const paramSites = member.parameters.map((p) => (0, structured_top_level_codecs_1.getStructuredParameterSite)(codecCatalog, service.name, member.name, p.name));
|
|
2754
|
+
const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(codecCatalog, service.name, member.name);
|
|
2755
|
+
const encodedArgs = member.parameters.length > 0
|
|
2756
|
+
? `[${member.parameters.map((p, index) => `${paramSites[index] ? `encode${paramSites[index].codecId}(${p.name})` : p.name}`).join(", ")}]`
|
|
2757
|
+
: "[]";
|
|
2100
2758
|
return ` ${service.name}_${member.name}(${args}${args ? ", " : ""}timeoutMs = this.defaultSlotTimeoutMs): Promise<${ret}> {
|
|
2101
|
-
return this.invokeSlot("${service.name}", "${member.name}", ${
|
|
2759
|
+
return this.invokeSlot("${service.name}", "${member.name}", ${encodedArgs}, timeoutMs).then((value) => ${payloadSite ? `decode${payloadSite.codecId}(value)` : `value as ${ret}`});
|
|
2102
2760
|
}`;
|
|
2103
2761
|
}))
|
|
2104
2762
|
.join("\n");
|
|
@@ -2107,8 +2765,9 @@ function renderNodeExpressWsIndex(spec) {
|
|
|
2107
2765
|
.filter((member) => member.kind === "Output" && member.payloadTypeText)
|
|
2108
2766
|
.map((member) => {
|
|
2109
2767
|
const typeText = mapTypeTextToTs(member.payloadTypeText);
|
|
2768
|
+
const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(codecCatalog, service.name, member.name);
|
|
2110
2769
|
return ` set${service.name}_${nodeCap(member.name)}(value: ${typeText}): void {
|
|
2111
|
-
this.setOutputValue("${service.name}", "${member.name}", value);
|
|
2770
|
+
this.setOutputValue("${service.name}", "${member.name}", ${payloadSite ? `encode${payloadSite.codecId}(value)` : "value"});
|
|
2112
2771
|
}`;
|
|
2113
2772
|
}))
|
|
2114
2773
|
.join("\n");
|
|
@@ -2161,8 +2820,9 @@ function renderNodeExpressWsIndex(spec) {
|
|
|
2161
2820
|
.filter((member) => (member.kind === "Input" || member.kind === "Output") && member.payloadTypeText)
|
|
2162
2821
|
.map((member) => {
|
|
2163
2822
|
const typeText = mapTypeTextToTs(member.payloadTypeText);
|
|
2823
|
+
const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(codecCatalog, service.name, member.name);
|
|
2164
2824
|
if (member.kind === "Input") {
|
|
2165
|
-
return ` ${member.name}: {\n get: () => session.readInput("${service.name}", "${member.name}") as
|
|
2825
|
+
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 },`;
|
|
2166
2826
|
}
|
|
2167
2827
|
return ` ${member.name}: {\n set: (value: ${typeText}) => session.set${service.name}_${nodeCap(member.name)}(value)\n },`;
|
|
2168
2828
|
})
|
|
@@ -2174,6 +2834,11 @@ function renderNodeExpressWsIndex(spec) {
|
|
|
2174
2834
|
.flatMap((service) => service.members
|
|
2175
2835
|
.filter((member) => member.kind === "Call" && member.payloadTypeText)
|
|
2176
2836
|
.map((member) => {
|
|
2837
|
+
const paramSites = member.parameters.map((p) => (0, structured_top_level_codecs_1.getStructuredParameterSite)(codecCatalog, service.name, member.name, p.name));
|
|
2838
|
+
const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(codecCatalog, service.name, member.name);
|
|
2839
|
+
const decodedArgs = member.parameters.length > 0
|
|
2840
|
+
? member.parameters.map((p, index) => `${paramSites[index] ? `decode${paramSites[index].codecId}(args[${index}])` : `args[${index}] as ${mapTypeTextToTs(p.typeText)}`}`).join(", ")
|
|
2841
|
+
: "";
|
|
2177
2842
|
return ` if (service === "${service.name}" && member === "${member.name}") {
|
|
2178
2843
|
const handler = implementation.${service.name}.${member.name};
|
|
2179
2844
|
if (typeof handler !== "function") {
|
|
@@ -2196,8 +2861,8 @@ function renderNodeExpressWsIndex(spec) {
|
|
|
2196
2861
|
});
|
|
2197
2862
|
throw err;
|
|
2198
2863
|
}
|
|
2199
|
-
Promise.resolve(handler(buildHandlerBridge(session)
|
|
2200
|
-
.then((result) => sendJson(session.socket, { type: "callResult", requestId, result }))
|
|
2864
|
+
Promise.resolve(handler(buildHandlerBridge(session)${decodedArgs ? `, ${decodedArgs}` : ""}))
|
|
2865
|
+
.then((result) => sendJson(session.socket, { type: "callResult", requestId, result: ${payloadSite ? `encode${payloadSite.codecId}(result)` : "result"} }))
|
|
2201
2866
|
.catch((error) => {
|
|
2202
2867
|
const message = error instanceof Error ? error.message : String(error);
|
|
2203
2868
|
emitDiagnostic({
|
|
@@ -2225,6 +2890,13 @@ function renderNodeExpressWsIndex(spec) {
|
|
|
2225
2890
|
.flatMap((service) => service.members
|
|
2226
2891
|
.filter((member) => member.kind === "Emitter")
|
|
2227
2892
|
.map((member) => {
|
|
2893
|
+
const paramSites = member.parameters.map((p) => (0, structured_top_level_codecs_1.getStructuredParameterSite)(codecCatalog, service.name, member.name, p.name));
|
|
2894
|
+
const decodedArgs = member.parameters.length > 0
|
|
2895
|
+
? member.parameters.map((p, index) => `${paramSites[index] ? `decode${paramSites[index].codecId}(args[${index}])` : `args[${index}] as ${mapTypeTextToTs(p.typeText)}`}`).join(", ")
|
|
2896
|
+
: "";
|
|
2897
|
+
const decodedSignalArgs = member.parameters.length > 0
|
|
2898
|
+
? `[${member.parameters.map((p, index) => `${paramSites[index] ? `decode${paramSites[index].codecId}(args[${index}])` : `args[${index}] as ${mapTypeTextToTs(p.typeText)}`}`).join(", ")}]`
|
|
2899
|
+
: "[]";
|
|
2228
2900
|
return ` if (service === "${service.name}" && member === "${member.name}") {
|
|
2229
2901
|
const handler = implementation.${service.name}.${member.name};
|
|
2230
2902
|
if (typeof handler !== "function") {
|
|
@@ -2241,7 +2913,8 @@ function renderNodeExpressWsIndex(spec) {
|
|
|
2241
2913
|
});
|
|
2242
2914
|
throw err;
|
|
2243
2915
|
}
|
|
2244
|
-
|
|
2916
|
+
session.emitSignal(service, member, ${decodedSignalArgs});
|
|
2917
|
+
void Promise.resolve(handler(buildHandlerBridge(session)${decodedArgs ? `, ${decodedArgs}` : ""})).catch((error) => {
|
|
2245
2918
|
const message = error instanceof Error ? error.message : String(error);
|
|
2246
2919
|
emitDiagnostic({
|
|
2247
2920
|
code: "EmitterHandlerError",
|
|
@@ -2262,6 +2935,7 @@ function renderNodeExpressWsIndex(spec) {
|
|
|
2262
2935
|
.flatMap((service) => service.members
|
|
2263
2936
|
.filter((member) => member.kind === "Input" && member.payloadTypeText)
|
|
2264
2937
|
.map((member) => {
|
|
2938
|
+
const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(codecCatalog, service.name, member.name);
|
|
2265
2939
|
return ` if (service === "${service.name}" && member === "${member.name}") {
|
|
2266
2940
|
const handler = implementation.${service.name}.${member.name};
|
|
2267
2941
|
if (typeof handler !== "function") {
|
|
@@ -2278,7 +2952,9 @@ function renderNodeExpressWsIndex(spec) {
|
|
|
2278
2952
|
});
|
|
2279
2953
|
throw err;
|
|
2280
2954
|
}
|
|
2281
|
-
|
|
2955
|
+
const decodedValue = ${payloadSite ? `decode${payloadSite.codecId}(value)` : `value as ${mapTypeTextToTs(member.payloadTypeText)}`};
|
|
2956
|
+
session.setInputState(service, member, decodedValue);
|
|
2957
|
+
void Promise.resolve(handler(buildHandlerBridge(session), decodedValue)).catch((error) => {
|
|
2282
2958
|
const message = error instanceof Error ? error.message : String(error);
|
|
2283
2959
|
emitDiagnostic({
|
|
2284
2960
|
code: "InputHandlerError",
|
|
@@ -2300,6 +2976,9 @@ import type { WebSocket, WebSocketServer } from "ws";
|
|
|
2300
2976
|
${typeImports}
|
|
2301
2977
|
${typeDecls}
|
|
2302
2978
|
|
|
2979
|
+
// Structured/top-level codec helpers
|
|
2980
|
+
${(0, structured_top_level_codecs_1.renderTsStructuredCodecHelpers)(codecCatalog)}
|
|
2981
|
+
|
|
2303
2982
|
${handlerInterfaces}
|
|
2304
2983
|
|
|
2305
2984
|
export interface ${spec.widgetName}NodeImplementation {
|
|
@@ -2666,7 +3345,6 @@ ${callDispatch}
|
|
|
2666
3345
|
const service = String(message.service ?? "");
|
|
2667
3346
|
const member = String(message.member ?? "");
|
|
2668
3347
|
const args = Array.isArray(message.args) ? (message.args as unknown[]) : [];
|
|
2669
|
-
session.emitSignal(service, member, args);
|
|
2670
3348
|
${emitterDispatch}
|
|
2671
3349
|
const err = new Error(\`No Emitter mapping found for \${service}.\${member}\`);
|
|
2672
3350
|
emitDiagnostic({
|
|
@@ -2685,7 +3363,6 @@ ${emitterDispatch}
|
|
|
2685
3363
|
const service = String(message.service ?? "");
|
|
2686
3364
|
const member = String(message.member ?? "");
|
|
2687
3365
|
const value = message.value;
|
|
2688
|
-
session.setInputState(service, member, value);
|
|
2689
3366
|
${inputDispatch}
|
|
2690
3367
|
const err = new Error(\`No Input mapping found for \${service}.\${member}\`);
|
|
2691
3368
|
emitDiagnostic({
|
|
@@ -2979,13 +3656,15 @@ function renderQtIntegrationCMake(widgetName) {
|
|
|
2979
3656
|
const generatedRootVar = "ANQST_GENERATED_WIDGET_DIR";
|
|
2980
3657
|
const generatedIncludeVar = "ANQST_GENERATED_INCLUDE_DIR";
|
|
2981
3658
|
const projectRootVar = "ANQST_PROJECT_ROOT";
|
|
3659
|
+
const requiredFilesVar = "ANQST_REQUIRED_GENERATED_FILES";
|
|
3660
|
+
const widgetBinaryDirVar = "ANQST_GENERATED_WIDGET_BINARY_DIR";
|
|
2982
3661
|
const widgetTarget = `${widgetName}Widget`;
|
|
2983
|
-
const autogenTarget = `${widgetTarget}_anqst_codegen`;
|
|
2984
3662
|
return `cmake_minimum_required(VERSION 3.21)
|
|
2985
3663
|
|
|
2986
3664
|
set(${projectRootVar} "\${CMAKE_CURRENT_LIST_DIR}/../../../../..")
|
|
2987
3665
|
set(${generatedRootVar} "\${CMAKE_CURRENT_LIST_DIR}/../qt/${generatedCppLibraryDirName(widgetName)}")
|
|
2988
3666
|
set(${generatedIncludeVar} "\${${generatedRootVar}}/include")
|
|
3667
|
+
set(${widgetBinaryDirVar} "\${CMAKE_CURRENT_BINARY_DIR}/${generatedCppLibraryDirName(widgetName)}")
|
|
2989
3668
|
|
|
2990
3669
|
if(TARGET ${widgetTarget})
|
|
2991
3670
|
return()
|
|
@@ -2995,66 +3674,27 @@ if(NOT TARGET anqstwebhostbase)
|
|
|
2995
3674
|
message(FATAL_ERROR "Target 'anqstwebhostbase' must exist before including generated AnQst CMake for ${widgetName}.")
|
|
2996
3675
|
endif()
|
|
2997
3676
|
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
set(CMAKE_AUTOUIC ON)
|
|
3001
|
-
set(CMAKE_AUTORCC ON)
|
|
3002
|
-
|
|
3003
|
-
find_program(ANQST_NPM_EXECUTABLE npm REQUIRED)
|
|
3004
|
-
find_program(ANQST_NPX_EXECUTABLE npx REQUIRED)
|
|
3005
|
-
|
|
3006
|
-
add_custom_command(
|
|
3007
|
-
OUTPUT
|
|
3008
|
-
"\${${generatedRootVar}}/CMakeLists.txt"
|
|
3009
|
-
"\${${generatedRootVar}}/${widgetName}.qrc"
|
|
3010
|
-
"\${${generatedRootVar}}/${widgetName}.cpp"
|
|
3011
|
-
"\${${generatedIncludeVar}}/${widgetName}.h"
|
|
3012
|
-
"\${${generatedIncludeVar}}/${widgetName}Widget.h"
|
|
3013
|
-
"\${${generatedIncludeVar}}/${widgetName}Types.h"
|
|
3014
|
-
"\${${generatedRootVar}}/webapp/index.html"
|
|
3015
|
-
COMMAND "\${ANQST_NPM_EXECUTABLE}" install
|
|
3016
|
-
COMMAND "\${ANQST_NPX_EXECUTABLE}" anqst build
|
|
3017
|
-
WORKING_DIRECTORY "\${${projectRootVar}}"
|
|
3018
|
-
COMMENT "Generating AnQst widget library (${widgetTarget}) from Angular project"
|
|
3019
|
-
VERBATIM
|
|
3020
|
-
)
|
|
3021
|
-
|
|
3022
|
-
add_custom_target(${autogenTarget}
|
|
3023
|
-
DEPENDS
|
|
3024
|
-
"\${${generatedRootVar}}/CMakeLists.txt"
|
|
3025
|
-
"\${${generatedRootVar}}/${widgetName}.qrc"
|
|
3026
|
-
"\${${generatedRootVar}}/${widgetName}.cpp"
|
|
3027
|
-
"\${${generatedIncludeVar}}/${widgetName}.h"
|
|
3028
|
-
"\${${generatedIncludeVar}}/${widgetName}Widget.h"
|
|
3029
|
-
"\${${generatedIncludeVar}}/${widgetName}Types.h"
|
|
3030
|
-
"\${${generatedRootVar}}/webapp/index.html"
|
|
3031
|
-
)
|
|
3032
|
-
|
|
3033
|
-
set_source_files_properties(
|
|
3677
|
+
set(${requiredFilesVar}
|
|
3678
|
+
"\${${generatedRootVar}}/CMakeLists.txt"
|
|
3034
3679
|
"\${${generatedRootVar}}/${widgetName}.qrc"
|
|
3035
3680
|
"\${${generatedRootVar}}/${widgetName}.cpp"
|
|
3036
3681
|
"\${${generatedIncludeVar}}/${widgetName}.h"
|
|
3037
3682
|
"\${${generatedIncludeVar}}/${widgetName}Widget.h"
|
|
3038
3683
|
"\${${generatedIncludeVar}}/${widgetName}Types.h"
|
|
3039
|
-
|
|
3684
|
+
"\${${generatedRootVar}}/webapp/index.html"
|
|
3040
3685
|
)
|
|
3041
3686
|
|
|
3042
|
-
|
|
3043
|
-
"\${
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
)
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
)
|
|
3054
|
-
target_link_libraries(${widgetTarget}
|
|
3055
|
-
PUBLIC
|
|
3056
|
-
anqstwebhostbase
|
|
3057
|
-
)
|
|
3687
|
+
foreach(required_file IN LISTS ${requiredFilesVar})
|
|
3688
|
+
if(NOT EXISTS "\${required_file}")
|
|
3689
|
+
message(FATAL_ERROR
|
|
3690
|
+
"Generated AnQst widget tree is incomplete for ${widgetName}. "
|
|
3691
|
+
"Missing file: \${required_file}. "
|
|
3692
|
+
"Run 'npx anqst build' in '\${${projectRootVar}}' first."
|
|
3693
|
+
)
|
|
3694
|
+
endif()
|
|
3695
|
+
endforeach()
|
|
3696
|
+
|
|
3697
|
+
add_subdirectory("\${${generatedRootVar}}" "\${${widgetBinaryDirVar}}")
|
|
3058
3698
|
`;
|
|
3059
3699
|
}
|
|
3060
3700
|
function installQtIntegrationCMake(cwd, widgetName) {
|
|
@@ -3273,7 +3913,7 @@ public:
|
|
|
3273
3913
|
" <widget class=\\"${widgetClass}\\" name=\\"${widgetName.toLowerCase()}\\">\\n"
|
|
3274
3914
|
" <property name=\\"minimumSize\\">\\n"
|
|
3275
3915
|
" <size>\\n"
|
|
3276
|
-
" <width>
|
|
3916
|
+
" <width>0</width>\\n"
|
|
3277
3917
|
" <height>128</height>\\n"
|
|
3278
3918
|
" </size>\\n"
|
|
3279
3919
|
" </property>\\n"
|