@dusted/anqst 0.1.1 → 0.1.3

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/dist/src/emit.js CHANGED
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.generateOutputs = generateOutputs;
7
7
  exports.writeGeneratedOutputs = writeGeneratedOutputs;
8
- exports.installTypeScriptOutputs = installTypeScriptOutputs;
9
8
  exports.installEmbeddedWebBundle = installEmbeddedWebBundle;
10
9
  exports.installQtIntegrationCMake = installQtIntegrationCMake;
11
10
  exports.installQtDesignerPluginCMake = installQtDesignerPluginCMake;
@@ -13,6 +12,8 @@ const node_fs_1 = __importDefault(require("node:fs"));
13
12
  const node_path_1 = __importDefault(require("node:path"));
14
13
  const typescript_1 = __importDefault(require("typescript"));
15
14
  const pngjs_1 = require("pngjs");
15
+ const build_stamp_1 = require("./build-stamp");
16
+ const layout_1 = require("./layout");
16
17
  function stripAnQstType(typeText) {
17
18
  return typeText
18
19
  .replace(/\bAnQst\.Type\.stringArray\b/g, "string[]")
@@ -349,7 +350,7 @@ function normalizeImportPathForGenerated(specFilePath, generatedFileRelPath, mod
349
350
  return moduleSpecifier;
350
351
  }
351
352
  const specDir = node_path_1.default.dirname(specFilePath);
352
- const generatedAbs = node_path_1.default.resolve(node_path_1.default.dirname(specFilePath), "generated_output", generatedFileRelPath);
353
+ const generatedAbs = node_path_1.default.resolve(node_path_1.default.dirname(specFilePath), "generated", generatedFileRelPath);
353
354
  const generatedDir = node_path_1.default.dirname(generatedAbs);
354
355
  const resolvedModulePath = node_path_1.default.resolve(specDir, moduleSpecifier);
355
356
  const relative = node_path_1.default.relative(generatedDir, resolvedModulePath);
@@ -652,13 +653,24 @@ ${decls}
652
653
  ${metatypes}
653
654
  `;
654
655
  }
656
+ function renderWidgetUmbrellaHeader(spec) {
657
+ return `#pragma once
658
+ // Built by <AnQst_version>
659
+ #include "${spec.widgetName}Widget.h"
660
+ #include "${spec.widgetName}Types.h"
661
+ `;
662
+ }
655
663
  function renderWidgetHeader(spec, cppTypes) {
664
+ const widgetClassName = `${spec.widgetName}Widget`;
656
665
  const callbackAliases = [];
657
666
  const publicMethods = [];
667
+ const slotMethods = [];
668
+ const handleMethods = [];
669
+ const callSetterMethods = [];
658
670
  const signals = [];
659
671
  const properties = [];
660
672
  const fields = [];
661
- const outputSetters = [];
673
+ const publicSlots = [];
662
674
  const bindings = [];
663
675
  for (const service of spec.services) {
664
676
  for (const member of service.members) {
@@ -666,21 +678,20 @@ function renderWidgetHeader(spec, cppTypes) {
666
678
  const memberPascal = pascalCase(member.name);
667
679
  if (member.kind === "Call" && member.payloadTypeText) {
668
680
  const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
669
- const args = member.parameters.map((p) => `${cppTypes.mapTypeText(p.typeText, [service.name, member.name, p.name])} ${p.name}`).join(", ");
681
+ const args = member.parameters.map((p) => `const ${cppTypes.mapTypeText(p.typeText, [service.name, member.name, p.name])}& ${p.name}`).join(", ");
670
682
  callbackAliases.push(`using ${memberPascal}Handler = std::function<${cppType}(${args})>;`);
671
- publicMethods.push(`void set${memberPascal}Handler(const ${memberPascal}Handler& handler);`);
683
+ handleMethods.push(` void ${member.name}(const ${memberPascal}Handler& handler) const;`);
684
+ callSetterMethods.push(`void set${memberPascal}CallHandler(const ${memberPascal}Handler& handler);`);
672
685
  fields.push(`${memberPascal}Handler m_${member.name}Handler;`);
673
686
  }
674
687
  else if (member.kind === "Emitter") {
675
- const args = member.parameters.map((p) => `${cppTypes.mapTypeText(p.typeText, [service.name, member.name, p.name])} ${p.name}`).join(", ");
676
- callbackAliases.push(`using ${memberPascal}Handler = std::function<void(${args})>;`);
677
- publicMethods.push(`void set${memberPascal}Handler(const ${memberPascal}Handler& handler);`);
678
- fields.push(`${memberPascal}Handler m_${member.name}Handler;`);
688
+ const args = member.parameters.map((p) => `const ${cppTypes.mapTypeText(p.typeText, [service.name, member.name, p.name])}& ${p.name}`).join(", ");
689
+ signals.push(`void ${member.name}(${args});`);
679
690
  }
680
691
  else if (member.kind === "Slot") {
681
692
  const ret = member.payloadTypeText ? cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]) : "void";
682
693
  const args = member.parameters.map((p) => `${cppTypes.mapTypeText(p.typeText, [service.name, member.name, p.name])} ${p.name}`).join(", ");
683
- publicMethods.push(`${ret} ${member.name}(${args}${args ? ", " : ""}bool* ok = nullptr, QString* error = nullptr);`);
694
+ slotMethods.push(`${ret} slot_${member.name}(${args});`);
684
695
  }
685
696
  else if ((member.kind === "Input" || member.kind === "Output") && member.payloadTypeText) {
686
697
  const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
@@ -696,13 +707,16 @@ function renderWidgetHeader(spec, cppTypes) {
696
707
  fields.push(`${memberPascal}Handler m_${member.name}Handler;`);
697
708
  }
698
709
  else {
699
- outputSetters.push(`void publish${memberPascal}(const ${cppType}& value);`);
710
+ publicSlots.push(`void ${member.name}Slot(const ${cppType}& value);`);
700
711
  }
701
712
  }
702
713
  }
703
714
  }
704
715
  return `#pragma once
716
+ #include <QDateTime>
705
717
  #include <QHash>
718
+ #include <QMetaMethod>
719
+ #include <QQueue>
706
720
  #include <QVariant>
707
721
  #include <QVariantList>
708
722
  #include <functional>
@@ -710,28 +724,54 @@ function renderWidgetHeader(spec, cppTypes) {
710
724
  #include "${spec.widgetName}Types.h"
711
725
 
712
726
  namespace ${spec.widgetName} {
727
+ } // namespace ${spec.widgetName}
728
+
729
+ using namespace ${spec.widgetName};
713
730
 
714
- class ${spec.widgetName} : public AnQstWebHostBase {
731
+ class ${widgetClassName} : public AnQstWebHostBase {
715
732
  Q_OBJECT
716
733
  ${properties.map((p) => ` ${p}`).join("\n")}
717
734
 
718
735
  public:
719
- explicit ${spec.widgetName}(QWidget* parent = nullptr);
720
- ~${spec.widgetName}() override;
736
+ ${callbackAliases.map((s) => ` ${s}`).join("\n")}
737
+
738
+ class handle {
739
+ public:
740
+ explicit handle(${widgetClassName}* owner) : m_owner(owner) {}
741
+ ${handleMethods.join("\n")}
742
+ private:
743
+ ${widgetClassName}* m_owner;
744
+ };
745
+
746
+ explicit ${widgetClassName}(QWidget* parent = nullptr);
747
+ ~${widgetClassName}() override;
721
748
  bool enableDebug();
722
749
  static constexpr const char* kBootstrapEntryPoint = "index.html";
723
750
  static constexpr const char* kBootstrapContentRoot = "qrc:/${spec.widgetName.toLowerCase()}";
724
751
  static constexpr const char* kBootstrapBridgeObject = "${spec.widgetName}Bridge";
752
+ static constexpr int kMaxQueuedCallsPerEndpoint = 1024;
725
753
 
726
- ${callbackAliases.map((s) => ` ${s}`).join("\n")}
754
+ handle handle;
727
755
  ${publicMethods.map((s) => ` ${s}`).join("\n")}
728
- ${outputSetters.map((s) => ` ${s}`).join("\n")}
756
+
757
+ public slots:
758
+ ${slotMethods.map((s) => ` ${s}`).join("\n")}
759
+ ${publicSlots.map((s) => ` ${s}`).join("\n")}
729
760
 
730
761
  signals:
731
762
  ${signals.map((s) => ` ${s}`).join("\n")}
732
763
  void diagnosticsForwarded(const QVariantMap& payload);
733
764
 
765
+ protected:
766
+ void connectNotify(const QMetaMethod& signal) override;
767
+ void disconnectNotify(const QMetaMethod& signal) override;
768
+
734
769
  private:
770
+ struct PendingCallInvocation {
771
+ QString requestId;
772
+ QVariantList args;
773
+ QDateTime enqueuedAt;
774
+ };
735
775
  struct BridgeBindingRow {
736
776
  const char* service;
737
777
  const char* member;
@@ -741,21 +781,37 @@ private:
741
781
  static constexpr int kBridgeBindingsCount = ${bindings.length};
742
782
  static QString makeBindingKey(const QString& service, const QString& member);
743
783
  void installBridgeBindings();
784
+ bool hasEmitterListeners(const QString& service, const QString& member) const;
744
785
  QVariant handleGeneratedCall(const QString& service, const QString& member, const QVariantList& args);
745
786
  void handleGeneratedEmitter(const QString& service, const QString& member, const QVariantList& args);
746
787
  void handleGeneratedInput(const QString& service, const QString& member, const QVariant& value);
747
-
788
+ QVariant waitForCallHandlerAndInvoke(
789
+ const QString& service,
790
+ const QString& member,
791
+ const QString& requestId,
792
+ int timeoutMs,
793
+ const std::function<QVariant()>& invokeNow);
794
+ void removeQueuedCallById(const QString& queueKey, const QString& requestId);
795
+ ${callSetterMethods.map((s) => ` ${s}`).join("\n")}
796
+
797
+ qulonglong m_callRequestCounter{0};
798
+ QHash<QString, QQueue<PendingCallInvocation>> m_queuedCalls;
748
799
  ${fields.map((f) => ` ${f}`).join("\n")}
749
800
  };
750
-
751
- } // namespace ${spec.widgetName}
752
801
  `;
753
802
  }
754
803
  function renderCppStub(spec, cppTypes) {
804
+ const widgetClassName = `${spec.widgetName}Widget`;
755
805
  const lines = [];
756
- lines.push(`#include "include/${spec.widgetName}.h"`);
806
+ lines.push(`#include "include/${spec.widgetName}Widget.h"`);
757
807
  lines.push(`#include <QDebug>`);
808
+ lines.push(`#include <QElapsedTimer>`);
809
+ lines.push(`#include <QEventLoop>`);
758
810
  lines.push(`#include <QMetaType>`);
811
+ lines.push(`#include <QTimer>`);
812
+ lines.push(`#include <stdexcept>`);
813
+ lines.push("");
814
+ lines.push(`using namespace ${spec.widgetName};`);
759
815
  lines.push("");
760
816
  lines.push(`extern int qInitResources_${spec.widgetName}();`);
761
817
  lines.push("");
@@ -772,9 +828,23 @@ function renderCppStub(spec, cppTypes) {
772
828
  lines.push("}");
773
829
  lines.push("}");
774
830
  lines.push("");
775
- lines.push(`namespace ${spec.widgetName} {`);
776
- lines.push("");
777
- lines.push(`const ${spec.widgetName}::BridgeBindingRow ${spec.widgetName}::kBridgeBindings[] = {`);
831
+ for (const service of spec.services) {
832
+ for (const member of service.members) {
833
+ if (member.kind !== "Call" || !member.payloadTypeText)
834
+ continue;
835
+ const pascal = pascalCase(member.name);
836
+ lines.push(`void ${widgetClassName}::handle::${member.name}(const ${pascal}Handler& handler) const {`);
837
+ lines.push(` if (m_owner == nullptr) return;`);
838
+ lines.push(` m_owner->set${pascal}CallHandler(handler);`);
839
+ lines.push(`}`);
840
+ lines.push("");
841
+ lines.push(`void ${widgetClassName}::set${pascal}CallHandler(const ${pascal}Handler& handler) {`);
842
+ lines.push(` m_${member.name}Handler = handler;`);
843
+ lines.push(`}`);
844
+ lines.push("");
845
+ }
846
+ }
847
+ lines.push(`const ${widgetClassName}::BridgeBindingRow ${widgetClassName}::kBridgeBindings[] = {`);
778
848
  for (const service of spec.services) {
779
849
  for (const member of service.members) {
780
850
  lines.push(` {"${service.name}", "${member.name}", "${member.kind}"},`);
@@ -782,7 +852,7 @@ function renderCppStub(spec, cppTypes) {
782
852
  }
783
853
  lines.push(`};`);
784
854
  lines.push("");
785
- lines.push(`${spec.widgetName}::${spec.widgetName}(QWidget* parent) : AnQstWebHostBase(parent) {`);
855
+ lines.push(`${widgetClassName}::${widgetClassName}(QWidget* parent) : AnQstWebHostBase(parent), handle(this) {`);
786
856
  lines.push(` static const bool kResourcesInitialized = []() {`);
787
857
  lines.push(` ::qInitResources_${spec.widgetName}();`);
788
858
  lines.push(` return true;`);
@@ -790,7 +860,7 @@ function renderCppStub(spec, cppTypes) {
790
860
  lines.push(` Q_UNUSED(kResourcesInitialized);`);
791
861
  lines.push(` registerGeneratedMetaTypes();`);
792
862
  lines.push(` installBridgeBindings();`);
793
- lines.push(` QObject::connect(this, &AnQstWebHostBase::onHostError, this, &${spec.widgetName}::diagnosticsForwarded);`);
863
+ lines.push(` QObject::connect(this, &AnQstWebHostBase::onHostError, this, &${widgetClassName}::diagnosticsForwarded);`);
794
864
  lines.push(` const bool rootOk = setContentRoot(QString::fromUtf8(kBootstrapContentRoot));`);
795
865
  lines.push(` const bool bridgeOk = setBridgeObject(this, QString::fromUtf8(kBootstrapBridgeObject));`);
796
866
  lines.push(` const bool loadOk = rootOk && bridgeOk && loadEntryPoint(QString::fromUtf8(kBootstrapEntryPoint));`);
@@ -799,17 +869,74 @@ function renderCppStub(spec, cppTypes) {
799
869
  lines.push(` }`);
800
870
  lines.push("}");
801
871
  lines.push("");
802
- lines.push(`${spec.widgetName}::~${spec.widgetName}() = default;`);
872
+ lines.push(`${widgetClassName}::~${widgetClassName}() = default;`);
803
873
  lines.push("");
804
- lines.push(`bool ${spec.widgetName}::enableDebug() {`);
874
+ lines.push(`bool ${widgetClassName}::enableDebug() {`);
805
875
  lines.push(` return AnQstWebHostBase::enableDebug();`);
806
876
  lines.push("}");
807
877
  lines.push("");
808
- lines.push(`QString ${spec.widgetName}::makeBindingKey(const QString& service, const QString& member) {`);
878
+ lines.push(`QString ${widgetClassName}::makeBindingKey(const QString& service, const QString& member) {`);
809
879
  lines.push(` return service + QStringLiteral("::") + member;`);
810
880
  lines.push(`}`);
811
881
  lines.push("");
812
- lines.push(`void ${spec.widgetName}::installBridgeBindings() {`);
882
+ lines.push(`void ${widgetClassName}::removeQueuedCallById(const QString& queueKey, const QString& requestId) {`);
883
+ lines.push(` if (!m_queuedCalls.contains(queueKey)) return;`);
884
+ lines.push(` auto& queue = m_queuedCalls[queueKey];`);
885
+ lines.push(` for (int i = 0; i < queue.size(); ++i) {`);
886
+ lines.push(` if (queue[i].requestId == requestId) {`);
887
+ lines.push(` queue.removeAt(i);`);
888
+ lines.push(` break;`);
889
+ lines.push(` }`);
890
+ lines.push(` }`);
891
+ lines.push(`}`);
892
+ lines.push("");
893
+ lines.push(`QVariant ${widgetClassName}::waitForCallHandlerAndInvoke(`);
894
+ lines.push(` const QString& service,`);
895
+ lines.push(` const QString& member,`);
896
+ lines.push(` const QString& requestId,`);
897
+ lines.push(` int timeoutMs,`);
898
+ lines.push(` const std::function<QVariant()>& invokeNow) {`);
899
+ lines.push(` const QString queueKey = makeBindingKey(service, member);`);
900
+ lines.push(` QElapsedTimer timer;`);
901
+ lines.push(` timer.start();`);
902
+ lines.push(` QEventLoop loop;`);
903
+ lines.push(` QTimer tick;`);
904
+ lines.push(` tick.setSingleShot(true);`);
905
+ lines.push(` QObject::connect(&tick, &QTimer::timeout, &loop, &QEventLoop::quit);`);
906
+ lines.push(` while (true) {`);
907
+ lines.push(` if (m_queuedCalls.contains(queueKey) && !m_queuedCalls[queueKey].isEmpty() && m_queuedCalls[queueKey].head().requestId == requestId) {`);
908
+ lines.push(` m_queuedCalls[queueKey].dequeue();`);
909
+ lines.push(` return invokeNow();`);
910
+ lines.push(` }`);
911
+ lines.push(` if (timeoutMs > 0 && timer.elapsed() >= timeoutMs) {`);
912
+ lines.push(` removeQueuedCallById(queueKey, requestId);`);
913
+ lines.push(` return QVariantMap{`);
914
+ lines.push(` {QStringLiteral("code"), QStringLiteral("BridgeTimeoutError")},`);
915
+ lines.push(` {QStringLiteral("message"), QStringLiteral("Call timed out while waiting for callback registration.")},`);
916
+ lines.push(` {QStringLiteral("service"), service},`);
917
+ lines.push(` {QStringLiteral("member"), member},`);
918
+ lines.push(` {QStringLiteral("requestId"), requestId}`);
919
+ lines.push(` };`);
920
+ lines.push(` }`);
921
+ lines.push(` tick.start(10);`);
922
+ lines.push(` loop.exec();`);
923
+ lines.push(` }`);
924
+ lines.push(`}`);
925
+ lines.push("");
926
+ lines.push(`bool ${widgetClassName}::hasEmitterListeners(const QString& service, const QString& member) const {`);
927
+ for (const service of spec.services) {
928
+ for (const member of service.members) {
929
+ if (member.kind !== "Emitter")
930
+ continue;
931
+ lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
932
+ lines.push(` return isSignalConnected(QMetaMethod::fromSignal(&${widgetClassName}::${member.name}));`);
933
+ lines.push(` }`);
934
+ }
935
+ }
936
+ lines.push(` return false;`);
937
+ lines.push(`}`);
938
+ lines.push("");
939
+ lines.push(`void ${widgetClassName}::installBridgeBindings() {`);
813
940
  lines.push(` setCallHandler([this](const QString& service, const QString& member, const QVariantList& args) -> QVariant {`);
814
941
  lines.push(` return handleGeneratedCall(service, member, args);`);
815
942
  lines.push(` });`);
@@ -821,55 +948,97 @@ function renderCppStub(spec, cppTypes) {
821
948
  lines.push(` });`);
822
949
  lines.push(`}`);
823
950
  lines.push("");
824
- lines.push(`QVariant ${spec.widgetName}::handleGeneratedCall(const QString& service, const QString& member, const QVariantList& args) {`);
951
+ lines.push(`QVariant ${widgetClassName}::handleGeneratedCall(const QString& service, const QString& member, const QVariantList& args) {`);
825
952
  for (const service of spec.services) {
826
953
  for (const member of service.members) {
827
954
  if (member.kind !== "Call" || !member.payloadTypeText)
828
955
  continue;
956
+ const timeoutMs = member.timeoutMs;
829
957
  const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
830
- const pascal = pascalCase(member.name);
831
958
  lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
832
- lines.push(` if (!m_${member.name}Handler) {`);
833
- lines.push(` if (property("anqstDesignerContext").toBool()) {`);
834
- lines.push(` return ${cppToVariantExpression(cppType, designerPlaceholderCppExpression(cppType, member.name))};`);
835
- lines.push(` }`);
836
- lines.push(` return QVariant();`);
837
- lines.push(` }`);
838
959
  for (let i = 0; i < member.parameters.length; i++) {
839
960
  const p = member.parameters[i];
840
961
  const pType = cppTypes.mapTypeText(p.typeText, [service.name, member.name, p.name]);
841
962
  lines.push(` const ${pType} ${p.name} = ${variantToCppExpression(pType, `args.value(${i})`)};`);
842
963
  }
843
- const argNames = member.parameters.map((p) => p.name).join(", ");
844
- lines.push(` const ${cppType} result = m_${member.name}Handler(${argNames});`);
845
- lines.push(` return ${cppToVariantExpression(cppType, "result")};`);
964
+ lines.push(` const QString requestId = QStringLiteral("call-%1").arg(++m_callRequestCounter);`);
965
+ lines.push(` const QString queueKey = makeBindingKey(QStringLiteral("${service.name}"), QStringLiteral("${member.name}"));`);
966
+ lines.push(` auto invokeNow = [this, requestId${member.parameters.length > 0 ? `, ${member.parameters.map((p) => p.name).join(", ")}` : ""}]() -> QVariant {`);
967
+ lines.push(` if (!m_${member.name}Handler) {`);
968
+ lines.push(` return QVariantMap{`);
969
+ lines.push(` {QStringLiteral("code"), QStringLiteral("HandlerNotRegisteredError")},`);
970
+ lines.push(` {QStringLiteral("message"), QStringLiteral("No callback registered for Call endpoint.")},`);
971
+ lines.push(` {QStringLiteral("service"), QStringLiteral("${service.name}")},`);
972
+ lines.push(` {QStringLiteral("member"), QStringLiteral("${member.name}")},`);
973
+ lines.push(` {QStringLiteral("requestId"), requestId}`);
974
+ lines.push(` };`);
975
+ lines.push(` }`);
976
+ lines.push(` try {`);
977
+ const callArgs = member.parameters.map((p) => p.name).join(", ");
978
+ lines.push(` const ${cppType} result = m_${member.name}Handler(${callArgs});`);
979
+ lines.push(` return ${cppToVariantExpression(cppType, "result")};`);
980
+ lines.push(` } catch (const std::exception& ex) {`);
981
+ lines.push(` return QVariantMap{`);
982
+ lines.push(` {QStringLiteral("code"), QStringLiteral("CallHandlerError")},`);
983
+ lines.push(` {QStringLiteral("message"), QString::fromUtf8(ex.what())},`);
984
+ lines.push(` {QStringLiteral("service"), QStringLiteral("${service.name}")},`);
985
+ lines.push(` {QStringLiteral("member"), QStringLiteral("${member.name}")},`);
986
+ lines.push(` {QStringLiteral("requestId"), requestId}`);
987
+ lines.push(` };`);
988
+ lines.push(` } catch (...) {`);
989
+ lines.push(` return QVariantMap{`);
990
+ lines.push(` {QStringLiteral("code"), QStringLiteral("CallHandlerError")},`);
991
+ lines.push(` {QStringLiteral("message"), QStringLiteral("Call handler threw unknown exception.")},`);
992
+ lines.push(` {QStringLiteral("service"), QStringLiteral("${service.name}")},`);
993
+ lines.push(` {QStringLiteral("member"), QStringLiteral("${member.name}")},`);
994
+ lines.push(` {QStringLiteral("requestId"), requestId}`);
995
+ lines.push(` };`);
996
+ lines.push(` }`);
997
+ lines.push(` };`);
998
+ lines.push(` if (m_${member.name}Handler) {`);
999
+ lines.push(` return invokeNow();`);
1000
+ lines.push(` }`);
1001
+ lines.push(` auto& queue = m_queuedCalls[queueKey];`);
1002
+ lines.push(` if (queue.size() >= kMaxQueuedCallsPerEndpoint) {`);
1003
+ lines.push(` queue.dequeue();`);
1004
+ lines.push(` }`);
1005
+ lines.push(` queue.enqueue(PendingCallInvocation{requestId, args, QDateTime::currentDateTimeUtc()});`);
1006
+ lines.push(` return waitForCallHandlerAndInvoke(QStringLiteral("${service.name}"), QStringLiteral("${member.name}"), requestId, ${timeoutMs}, invokeNow);`);
846
1007
  lines.push(` }`);
847
1008
  }
848
1009
  }
849
- lines.push(` return QVariant();`);
1010
+ lines.push(` return QVariantMap{`);
1011
+ lines.push(` {QStringLiteral("code"), QStringLiteral("HandlerNotRegisteredError")},`);
1012
+ lines.push(` {QStringLiteral("message"), QStringLiteral("No Call mapping found.")},`);
1013
+ lines.push(` {QStringLiteral("service"), service},`);
1014
+ lines.push(` {QStringLiteral("member"), member},`);
1015
+ lines.push(` {QStringLiteral("requestId"), QString()}`);
1016
+ lines.push(` };`);
850
1017
  lines.push(`}`);
851
1018
  lines.push("");
852
- lines.push(`void ${spec.widgetName}::handleGeneratedEmitter(const QString& service, const QString& member, const QVariantList& args) {`);
1019
+ lines.push(`void ${widgetClassName}::handleGeneratedEmitter(const QString& service, const QString& member, const QVariantList& args) {`);
1020
+ lines.push(` if (!hasEmitterListeners(service, member)) {`);
1021
+ lines.push(` return;`);
1022
+ lines.push(` }`);
853
1023
  for (const service of spec.services) {
854
1024
  for (const member of service.members) {
855
1025
  if (member.kind !== "Emitter")
856
1026
  continue;
857
1027
  lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
858
- lines.push(` if (!m_${member.name}Handler) return;`);
859
1028
  for (let i = 0; i < member.parameters.length; i++) {
860
1029
  const p = member.parameters[i];
861
1030
  const pType = cppTypes.mapTypeText(p.typeText, [service.name, member.name, p.name]);
862
1031
  lines.push(` const ${pType} ${p.name} = ${variantToCppExpression(pType, `args.value(${i})`)};`);
863
1032
  }
864
1033
  const argNames = member.parameters.map((p) => p.name).join(", ");
865
- lines.push(` m_${member.name}Handler(${argNames});`);
1034
+ lines.push(` emit ${member.name}(${argNames});`);
866
1035
  lines.push(` return;`);
867
1036
  lines.push(` }`);
868
1037
  }
869
1038
  }
870
1039
  lines.push(`}`);
871
1040
  lines.push("");
872
- lines.push(`void ${spec.widgetName}::handleGeneratedInput(const QString& service, const QString& member, const QVariant& value) {`);
1041
+ lines.push(`void ${widgetClassName}::handleGeneratedInput(const QString& service, const QString& member, const QVariant& value) {`);
873
1042
  for (const service of spec.services) {
874
1043
  for (const member of service.members) {
875
1044
  if (member.kind !== "Input" || !member.payloadTypeText)
@@ -888,20 +1057,8 @@ function renderCppStub(spec, cppTypes) {
888
1057
  for (const service of spec.services) {
889
1058
  for (const member of service.members) {
890
1059
  const memberPascal = pascalCase(member.name);
891
- if ((member.kind === "Call" || member.kind === "Input") && member.payloadTypeText) {
892
- lines.push(`void ${spec.widgetName}::set${memberPascal}Handler(const ${memberPascal}Handler& handler) {`);
893
- lines.push(` m_${member.name}Handler = handler;`);
894
- lines.push("}");
895
- lines.push("");
896
- }
897
- else if (member.kind === "Emitter") {
898
- lines.push(`void ${spec.widgetName}::set${memberPascal}Handler(const ${memberPascal}Handler& handler) {`);
899
- lines.push(` m_${member.name}Handler = handler;`);
900
- lines.push("}");
901
- lines.push("");
902
- }
903
- else if (member.kind === "Input" && member.payloadTypeText) {
904
- lines.push(`void ${spec.widgetName}::set${memberPascal}Handler(const ${memberPascal}Handler& handler) {`);
1060
+ if (member.kind === "Input" && member.payloadTypeText) {
1061
+ lines.push(`void ${widgetClassName}::set${memberPascal}Handler(const ${memberPascal}Handler& handler) {`);
905
1062
  lines.push(` m_${member.name}Handler = handler;`);
906
1063
  lines.push("}");
907
1064
  lines.push("");
@@ -909,8 +1066,7 @@ function renderCppStub(spec, cppTypes) {
909
1066
  if (member.kind === "Slot") {
910
1067
  const ret = member.payloadTypeText ? cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]) : "void";
911
1068
  const args = member.parameters.map((p) => `${cppTypes.mapTypeText(p.typeText, [service.name, member.name, p.name])} ${p.name}`).join(", ");
912
- const argsWithMeta = `${args}${args ? ", " : ""}bool* ok, QString* error`;
913
- lines.push(`${ret} ${spec.widgetName}::${member.name}(${argsWithMeta}) {`);
1069
+ lines.push(`${ret} ${widgetClassName}::slot_${member.name}(${args}) {`);
914
1070
  lines.push(` QVariantList invokeArgs;`);
915
1071
  for (const p of member.parameters) {
916
1072
  const pType = mapTsTypeToCpp(p.typeText);
@@ -919,14 +1075,18 @@ function renderCppStub(spec, cppTypes) {
919
1075
  lines.push(` QVariant result;`);
920
1076
  lines.push(` QString invokeError;`);
921
1077
  lines.push(` const bool success = invokeSlot(QStringLiteral("${service.name}"), QStringLiteral("${member.name}"), invokeArgs, &result, &invokeError);`);
922
- lines.push(` if (ok != nullptr) *ok = success;`);
923
- lines.push(` if (error != nullptr) *error = invokeError;`);
1078
+ lines.push(` if (!success) {`);
1079
+ lines.push(` if (invokeError == QStringLiteral("slot invocation timeout")) {`);
1080
+ lines.push(` const QString timeoutMsg = QStringLiteral("[Timeout] ${service.name}.${member.name}: The webapp inside the widget did not anwser within %1 ms.").arg(slotInvocationTimeoutMs());`);
1081
+ lines.push(` throw std::runtime_error(timeoutMsg.toStdString());`);
1082
+ lines.push(` }`);
1083
+ lines.push(` const QString requestFailed = QStringLiteral("[RequestFailed]: %1").arg(invokeError);`);
1084
+ lines.push(` throw std::runtime_error(requestFailed.toStdString());`);
1085
+ lines.push(` }`);
924
1086
  if (ret === "void") {
925
- lines.push(` if (!success) return;`);
926
1087
  lines.push(` return;`);
927
1088
  }
928
1089
  else {
929
- lines.push(` if (!success) return ${ret}{};`);
930
1090
  lines.push(` return ${variantToCppExpression(ret, "result")};`);
931
1091
  }
932
1092
  lines.push("}");
@@ -935,11 +1095,11 @@ function renderCppStub(spec, cppTypes) {
935
1095
  else if ((member.kind === "Input" || member.kind === "Output") && member.payloadTypeText) {
936
1096
  const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
937
1097
  const cap = member.name.charAt(0).toUpperCase() + member.name.slice(1);
938
- lines.push(`${cppType} ${spec.widgetName}::${member.name}() const {`);
1098
+ lines.push(`${cppType} ${widgetClassName}::${member.name}() const {`);
939
1099
  lines.push(` return m_${member.name};`);
940
1100
  lines.push("}");
941
1101
  lines.push("");
942
- lines.push(`void ${spec.widgetName}::set${cap}(const ${cppType}& value) {`);
1102
+ lines.push(`void ${widgetClassName}::set${cap}(const ${cppType}& value) {`);
943
1103
  lines.push(` if (m_${member.name} == value) return;`);
944
1104
  lines.push(` m_${member.name} = value;`);
945
1105
  if (member.kind === "Output") {
@@ -949,7 +1109,7 @@ function renderCppStub(spec, cppTypes) {
949
1109
  lines.push("}");
950
1110
  lines.push("");
951
1111
  if (member.kind === "Output") {
952
- lines.push(`void ${spec.widgetName}::publish${pascalCase(member.name)}(const ${cppType}& value) {`);
1112
+ lines.push(`void ${widgetClassName}::${member.name}Slot(const ${cppType}& value) {`);
953
1113
  lines.push(` set${cap}(value);`);
954
1114
  lines.push(`}`);
955
1115
  lines.push("");
@@ -957,7 +1117,16 @@ function renderCppStub(spec, cppTypes) {
957
1117
  }
958
1118
  }
959
1119
  }
960
- lines.push(`} // namespace ${spec.widgetName}`);
1120
+ lines.push(`void ${widgetClassName}::connectNotify(const QMetaMethod& signal) {`);
1121
+ lines.push(` AnQstWebHostBase::connectNotify(signal);`);
1122
+ lines.push(` Q_UNUSED(signal);`);
1123
+ lines.push(`}`);
1124
+ lines.push("");
1125
+ lines.push(`void ${widgetClassName}::disconnectNotify(const QMetaMethod& signal) {`);
1126
+ lines.push(` AnQstWebHostBase::disconnectNotify(signal);`);
1127
+ lines.push(` Q_UNUSED(signal);`);
1128
+ lines.push(`}`);
1129
+ lines.push("");
961
1130
  return lines.join("\n");
962
1131
  }
963
1132
  function renderCMake(spec) {
@@ -977,6 +1146,7 @@ add_library(${spec.widgetName}Widget
977
1146
  ${spec.widgetName}.cpp
978
1147
  ${spec.widgetName}.qrc
979
1148
  include/${spec.widgetName}.h
1149
+ include/${spec.widgetName}Widget.h
980
1150
  include/${spec.widgetName}Types.h
981
1151
  )
982
1152
  target_include_directories(${spec.widgetName}Widget
@@ -1002,24 +1172,7 @@ function normalizeSlashes(value) {
1002
1172
  return value.split(node_path_1.default.sep).join("/");
1003
1173
  }
1004
1174
  function resolveActiveBuildStamp() {
1005
- const fromEnv = process.env.ANQST_BUILD_STAMP?.trim();
1006
- if (fromEnv && fromEnv.length > 0) {
1007
- return fromEnv;
1008
- }
1009
- const activePath = node_path_1.default.resolve(__dirname, "..", "..", ".anqstgen-version-active.json");
1010
- if (!node_fs_1.default.existsSync(activePath)) {
1011
- return "";
1012
- }
1013
- try {
1014
- const parsed = JSON.parse(node_fs_1.default.readFileSync(activePath, "utf8"));
1015
- if (typeof parsed.active === "string" && parsed.active.trim().length > 0) {
1016
- return parsed.active.trim();
1017
- }
1018
- }
1019
- catch {
1020
- return "";
1021
- }
1022
- return "";
1175
+ return build_stamp_1.ANQST_BUILD_STAMP.trim();
1023
1176
  }
1024
1177
  function withBuildStamp(relativePath, content) {
1025
1178
  const stamp = resolveActiveBuildStamp();
@@ -1114,6 +1267,12 @@ function renderLocalTypeImports(spec) {
1114
1267
  return "";
1115
1268
  return `import type { ${localTypeNames.join(", ")} } from "./types";`;
1116
1269
  }
1270
+ function slotHandlerReturnType(tsRet) {
1271
+ if (tsRet === "void") {
1272
+ return "void | Promise<void> | Error";
1273
+ }
1274
+ return `${tsRet} | Promise<${tsRet}> | Error`;
1275
+ }
1117
1276
  function renderTsService(spec, serviceName) {
1118
1277
  const members = spec.services.find((s) => s.name === serviceName)?.members ?? [];
1119
1278
  const fieldLines = [];
@@ -1137,7 +1296,7 @@ function renderTsService(spec, serviceName) {
1137
1296
  }
1138
1297
  if (m.kind === "Slot") {
1139
1298
  const ret = mapTypeTextToTs(m.payloadTypeText ?? "void");
1140
- onSlotMembers.push(` ${m.name}: (handler: (${args}) => ${ret}): void => {`);
1299
+ onSlotMembers.push(` ${m.name}: (handler: (${args}) => ${slotHandlerReturnType(ret)}): void => {`);
1141
1300
  onSlotMembers.push(` this._bridge.registerSlot("${serviceName}", "${m.name}", handler as (...args: unknown[]) => unknown);`);
1142
1301
  onSlotMembers.push(" },");
1143
1302
  continue;
@@ -1197,7 +1356,7 @@ function renderTsServiceDts(spec, serviceName) {
1197
1356
  }
1198
1357
  if (m.kind === "Slot") {
1199
1358
  const ret = mapTypeTextToTs(m.payloadTypeText ?? "void");
1200
- onSlotMembers.push(` ${m.name}(handler: (${args}) => ${ret}): void;`);
1359
+ onSlotMembers.push(` ${m.name}(handler: (${args}) => ${slotHandlerReturnType(ret)}): void;`);
1201
1360
  continue;
1202
1361
  }
1203
1362
  if ((m.kind === "Input" || m.kind === "Output") && m.payloadTypeText) {
@@ -1226,7 +1385,7 @@ export declare class ${serviceName} {
1226
1385
  }
1227
1386
  function renderTsServices(spec) {
1228
1387
  const serviceClasses = spec.services.map((s) => renderTsService(spec, s.name)).join("\n");
1229
- const externalTypeImports = renderRequiredTypeImports(spec, "npmpackage/services.ts").trim();
1388
+ const externalTypeImports = renderRequiredTypeImports(spec, `frontend/${(0, layout_1.generatedFrontendDirName)(spec.widgetName)}/services.ts`).trim();
1230
1389
  const localTypeImports = renderLocalTypeImports(spec).trim();
1231
1390
  const typeImports = [externalTypeImports, localTypeImports].filter((s) => s.length > 0).join("\n");
1232
1391
  const typeImportsBlock = typeImports.length > 0 ? `${typeImports}\n\n` : "";
@@ -1267,6 +1426,24 @@ interface BridgeAdapter {
1267
1426
  onSlotInvocation(handler: SlotInvocationListener): void;
1268
1427
  }
1269
1428
 
1429
+ function isBridgeCallError(value: unknown): value is {
1430
+ code: unknown;
1431
+ message: unknown;
1432
+ service: unknown;
1433
+ member: unknown;
1434
+ requestId: unknown;
1435
+ } {
1436
+ if (value === null || typeof value !== "object") return false;
1437
+ const row = value as Record<string, unknown>;
1438
+ return (
1439
+ Object.prototype.hasOwnProperty.call(row, "code")
1440
+ && Object.prototype.hasOwnProperty.call(row, "message")
1441
+ && Object.prototype.hasOwnProperty.call(row, "service")
1442
+ && Object.prototype.hasOwnProperty.call(row, "member")
1443
+ && Object.prototype.hasOwnProperty.call(row, "requestId")
1444
+ );
1445
+ }
1446
+
1270
1447
  class QtWebChannelAdapter implements BridgeAdapter {
1271
1448
  private constructor(private readonly host: HostBridgeApi) {}
1272
1449
 
@@ -1300,8 +1477,14 @@ class QtWebChannelAdapter implements BridgeAdapter {
1300
1477
  }
1301
1478
 
1302
1479
  async call<T>(service: string, member: string, args: unknown[]): Promise<T> {
1303
- return new Promise<T>((resolve) => {
1304
- this.host.anQstBridge_call(service, member, args, (result) => resolve(result as T));
1480
+ return new Promise<T>((resolve, reject) => {
1481
+ this.host.anQstBridge_call(service, member, args, (result) => {
1482
+ if (isBridgeCallError(result)) {
1483
+ reject(result);
1484
+ return;
1485
+ }
1486
+ resolve(result as T);
1487
+ });
1305
1488
  });
1306
1489
  }
1307
1490
 
@@ -1331,7 +1514,13 @@ class QtWebChannelAdapter implements BridgeAdapter {
1331
1514
  }
1332
1515
 
1333
1516
  class WebSocketBridgeAdapter implements BridgeAdapter {
1334
- private readonly pending = new Map<string, (result: unknown) => void>();
1517
+ private readonly pending = new Map<string, {
1518
+ service: string;
1519
+ member: string;
1520
+ requestId: string;
1521
+ resolve: (result: unknown) => void;
1522
+ reject: (error: unknown) => void;
1523
+ }>();
1335
1524
  private readonly outputListeners: OutputListener[] = [];
1336
1525
  private readonly slotListeners: SlotInvocationListener[] = [];
1337
1526
  private requestCounter = 0;
@@ -1343,10 +1532,15 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
1343
1532
  const type = String(message["type"] ?? "");
1344
1533
  if (type === "callResult") {
1345
1534
  const requestId = String(message["requestId"] ?? "");
1346
- const resolver = this.pending.get(requestId);
1347
- if (resolver) {
1535
+ const pending = this.pending.get(requestId);
1536
+ if (pending) {
1348
1537
  this.pending.delete(requestId);
1349
- resolver(message["result"]);
1538
+ const result = message["result"];
1539
+ if (isBridgeCallError(result)) {
1540
+ pending.reject(result);
1541
+ return;
1542
+ }
1543
+ pending.resolve(result);
1350
1544
  }
1351
1545
  return;
1352
1546
  }
@@ -1377,6 +1571,18 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
1377
1571
  this.socket.close();
1378
1572
  }
1379
1573
  });
1574
+ this.socket.addEventListener("close", () => {
1575
+ for (const pending of this.pending.values()) {
1576
+ pending.reject({
1577
+ code: "BridgeDisconnectedError",
1578
+ message: "Bridge disconnected before call completion.",
1579
+ service: pending.service,
1580
+ member: pending.member,
1581
+ requestId: pending.requestId
1582
+ });
1583
+ }
1584
+ this.pending.clear();
1585
+ });
1380
1586
  }
1381
1587
 
1382
1588
  static async create(): Promise<WebSocketBridgeAdapter> {
@@ -1408,8 +1614,14 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
1408
1614
  async call<T>(service: string, member: string, args: unknown[]): Promise<T> {
1409
1615
  const requestId = \`req-\${++this.requestCounter}\`;
1410
1616
  const payload = { type: "call", requestId, service, member, args };
1411
- return await new Promise<T>((resolve) => {
1412
- this.pending.set(requestId, (value) => resolve(value as T));
1617
+ return await new Promise<T>((resolve, reject) => {
1618
+ this.pending.set(requestId, {
1619
+ service,
1620
+ member,
1621
+ requestId,
1622
+ resolve: (value) => resolve(value as T),
1623
+ reject
1624
+ });
1413
1625
  this.socket.send(JSON.stringify(payload));
1414
1626
  });
1415
1627
  }
@@ -1520,7 +1732,7 @@ class AnQstBridgeRuntime {
1520
1732
  outputHandler(value);
1521
1733
  }
1522
1734
  });
1523
- this.adapter.onSlotInvocation((requestId, service, member, args) => {
1735
+ this.adapter.onSlotInvocation(async (requestId, service, member, args) => {
1524
1736
  const key = this.key(service, member);
1525
1737
  const handler = this.slotHandlers.get(key);
1526
1738
  if (handler === undefined) {
@@ -1528,10 +1740,15 @@ class AnQstBridgeRuntime {
1528
1740
  return;
1529
1741
  }
1530
1742
  try {
1531
- const result = handler(...args);
1743
+ const result = await Promise.resolve(handler(...args));
1744
+ if (result instanceof Error) {
1745
+ this.adapter!.resolveSlot(requestId, false, undefined, result.message);
1746
+ return;
1747
+ }
1532
1748
  this.adapter!.resolveSlot(requestId, true, result, "");
1533
1749
  } catch (error) {
1534
- this.adapter!.resolveSlot(requestId, false, undefined, String(error));
1750
+ const message = error instanceof Error ? error.message : String(error);
1751
+ this.adapter!.resolveSlot(requestId, false, undefined, message);
1535
1752
  }
1536
1753
  });
1537
1754
  for (const key of this.slotHandlers.keys()) {
@@ -1551,13 +1768,13 @@ ${serviceClasses}
1551
1768
  `;
1552
1769
  }
1553
1770
  function renderTsTypes(spec) {
1554
- const typeImports = renderRequiredTypeImports(spec, "npmpackage/types.ts").trim();
1771
+ const typeImports = renderRequiredTypeImports(spec, `frontend/${(0, layout_1.generatedFrontendDirName)(spec.widgetName)}/types.ts`).trim();
1555
1772
  const typeDecls = renderTypeDeclarations(spec, true).trim();
1556
1773
  const sections = [typeImports, typeDecls].filter((s) => s.length > 0);
1557
1774
  return sections.length > 0 ? `${sections.join("\n\n")}\n` : "";
1558
1775
  }
1559
1776
  function renderTypeServicesDts(spec) {
1560
- const externalTypeImports = renderRequiredTypeImports(spec, "npmpackage/types/services.d.ts").trim();
1777
+ const externalTypeImports = renderRequiredTypeImports(spec, `frontend/${(0, layout_1.generatedFrontendDirName)(spec.widgetName)}/types/services.d.ts`).trim();
1561
1778
  const localTypeImports = renderLocalTypeImports(spec).trim();
1562
1779
  const serviceDecls = spec.services
1563
1780
  .map((s) => renderTsServiceDts(spec, s.name))
@@ -1566,7 +1783,7 @@ function renderTypeServicesDts(spec) {
1566
1783
  return sections.length > 0 ? `${sections.join("\n\n")}\n` : "";
1567
1784
  }
1568
1785
  function renderTypeTypesDts(spec) {
1569
- const typeImports = renderRequiredTypeImports(spec, "npmpackage/types/types.d.ts").trim();
1786
+ const typeImports = renderRequiredTypeImports(spec, `frontend/${(0, layout_1.generatedFrontendDirName)(spec.widgetName)}/types/types.d.ts`).trim();
1570
1787
  const typeDecls = renderTypeDeclarations(spec, true).trim();
1571
1788
  const sections = [typeImports, typeDecls].filter((s) => s.length > 0);
1572
1789
  return sections.length > 0 ? `${sections.join("\n\n")}\n` : "";
@@ -1632,13 +1849,13 @@ function nodeCap(value) {
1632
1849
  return value.length === 0 ? value : `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
1633
1850
  }
1634
1851
  function renderNodeExpressWsTypes(spec) {
1635
- const typeImports = renderRequiredTypeImports(spec, `${generatedNodeExpressWsDirName(spec.widgetName)}/types/index.d.ts`).trim();
1852
+ const typeImports = renderRequiredTypeImports(spec, `backend/node/express/${generatedNodeExpressWsDirName(spec.widgetName)}/types/index.d.ts`).trim();
1636
1853
  const typeDecls = renderTypeDeclarations(spec, true).trim();
1637
1854
  const sections = [typeImports, typeDecls].filter((s) => s.length > 0);
1638
1855
  return sections.length > 0 ? `${sections.join("\n\n")}\n` : "";
1639
1856
  }
1640
1857
  function renderNodeExpressWsIndex(spec) {
1641
- const typeImports = renderRequiredTypeImports(spec, `${generatedNodeExpressWsDirName(spec.widgetName)}/index.ts`);
1858
+ const typeImports = renderRequiredTypeImports(spec, `backend/node/express/${generatedNodeExpressWsDirName(spec.widgetName)}/index.ts`);
1642
1859
  const typeDecls = renderTypeDeclarations(spec, true);
1643
1860
  const handlerBridgeTypeName = `${spec.widgetName}HandlerBridge`;
1644
1861
  const sessionBridgeTypeName = `${spec.widgetName}SessionBridge`;
@@ -1764,7 +1981,7 @@ function renderNodeExpressWsIndex(spec) {
1764
1981
  sendJson(session.socket, {
1765
1982
  type: "callResult",
1766
1983
  requestId,
1767
- result: { __anqstError: { code: "HandlerNotRegisteredError", message: err.message, service, member } }
1984
+ result: { code: "HandlerNotRegisteredError", message: err.message, service, member, requestId }
1768
1985
  });
1769
1986
  throw err;
1770
1987
  }
@@ -1786,7 +2003,7 @@ function renderNodeExpressWsIndex(spec) {
1786
2003
  sendJson(session.socket, {
1787
2004
  type: "callResult",
1788
2005
  requestId,
1789
- result: { __anqstError: { code: "CallHandlerError", message, service, member } }
2006
+ result: { code: "CallHandlerError", message, service, member, requestId }
1790
2007
  });
1791
2008
  });
1792
2009
  return;
@@ -2138,7 +2355,7 @@ export interface ${spec.widgetName}NodeBridge {
2138
2355
  export function create${spec.widgetName}NodeExpressWsBridge(options: ${spec.widgetName}NodeBridgeOptions): ${spec.widgetName}NodeBridge {
2139
2356
  const wsPath = options.wsPath ?? "/anqst-bridge";
2140
2357
  const devConfigPath = options.devConfigPath ?? "/anqst-dev-config.json";
2141
- const defaultSlotTimeoutMs = options.defaultSlotTimeoutMs ?? 120000;
2358
+ const defaultSlotTimeoutMs = options.defaultSlotTimeoutMs ?? 1000;
2142
2359
  const maxQueuedPerSlot = options.maxQueuedSlotInvocationsPerSlot ?? 1024;
2143
2360
  const sessions = new Map<WebSocket, ${spec.widgetName}NodeSession>();
2144
2361
  const diagnosticListeners = new Set<(diagnostic: AnQstDiagnostic) => void>();
@@ -2230,7 +2447,7 @@ ${callDispatch}
2230
2447
  sendJson(session.socket, {
2231
2448
  type: "callResult",
2232
2449
  requestId,
2233
- result: { __anqstError: { code: "HandlerNotRegisteredError", message: err.message, service, member } }
2450
+ result: { code: "HandlerNotRegisteredError", message: err.message, service, member, requestId }
2234
2451
  });
2235
2452
  throw err;
2236
2453
  }
@@ -2337,32 +2554,34 @@ function renderTypeRootIndexDts(spec) {
2337
2554
  return sections.length > 0 ? `${sections.join("\n\n")}\n` : "";
2338
2555
  }
2339
2556
  function generatedCppLibraryDirName(widgetName) {
2340
- return `${widgetName}_QtWidget`;
2557
+ return (0, layout_1.generatedQtWidgetDirName)(widgetName);
2341
2558
  }
2342
2559
  function generatedNodeExpressWsDirName(widgetName) {
2343
- return `${widgetName}_node_express_ws`;
2560
+ return (0, layout_1.generatedNodeExpressDirName)(widgetName);
2344
2561
  }
2345
2562
  function generateOutputs(spec, options = { emitQWidget: true, emitAngularService: true, emitNodeExpressWs: false }) {
2346
- const cppDir = generatedCppLibraryDirName(spec.widgetName);
2347
- const nodeDir = generatedNodeExpressWsDirName(spec.widgetName);
2563
+ const frontendDir = `frontend/${(0, layout_1.generatedFrontendDirName)(spec.widgetName)}`;
2564
+ const cppDir = `backend/cpp/qt/${generatedCppLibraryDirName(spec.widgetName)}`;
2565
+ const nodeDir = `backend/node/express/${generatedNodeExpressWsDirName(spec.widgetName)}`;
2348
2566
  const outputs = {};
2349
2567
  if (options.emitAngularService) {
2350
- outputs["npmpackage/package.json"] = renderNpmPackage(spec);
2351
- outputs["npmpackage/index.ts"] = renderTsIndex();
2352
- outputs["npmpackage/services.ts"] = renderTsServices(spec);
2353
- outputs["npmpackage/types.ts"] = renderTsTypes(spec);
2354
- outputs["npmpackage/index.js"] = renderJsIndex();
2355
- outputs["npmpackage/services.js"] = renderJsServices();
2356
- outputs["npmpackage/types.js"] = renderJsTypes();
2357
- outputs["npmpackage/types/index.d.ts"] = renderTypeRootIndexDts(spec);
2358
- outputs["npmpackage/types/services.d.ts"] = renderTypeServicesDts(spec);
2359
- outputs["npmpackage/types/types.d.ts"] = renderTypeTypesDts(spec);
2568
+ outputs[`${frontendDir}/package.json`] = renderNpmPackage(spec);
2569
+ outputs[`${frontendDir}/index.ts`] = renderTsIndex();
2570
+ outputs[`${frontendDir}/services.ts`] = renderTsServices(spec);
2571
+ outputs[`${frontendDir}/types.ts`] = renderTsTypes(spec);
2572
+ outputs[`${frontendDir}/index.js`] = renderJsIndex();
2573
+ outputs[`${frontendDir}/services.js`] = renderJsServices();
2574
+ outputs[`${frontendDir}/types.js`] = renderJsTypes();
2575
+ outputs[`${frontendDir}/types/index.d.ts`] = renderTypeRootIndexDts(spec);
2576
+ outputs[`${frontendDir}/types/services.d.ts`] = renderTypeServicesDts(spec);
2577
+ outputs[`${frontendDir}/types/types.d.ts`] = renderTypeTypesDts(spec);
2360
2578
  }
2361
2579
  if (options.emitQWidget) {
2362
2580
  const cppTypes = buildCppTypeContext(spec);
2363
2581
  outputs[`${cppDir}/CMakeLists.txt`] = renderCMake(spec);
2364
2582
  outputs[`${cppDir}/${spec.widgetName}.qrc`] = renderEmbeddedQrc(spec.widgetName, []);
2365
- outputs[`${cppDir}/include/${spec.widgetName}.h`] = renderWidgetHeader(spec, cppTypes);
2583
+ outputs[`${cppDir}/include/${spec.widgetName}.h`] = renderWidgetUmbrellaHeader(spec);
2584
+ outputs[`${cppDir}/include/${spec.widgetName}Widget.h`] = renderWidgetHeader(spec, cppTypes);
2366
2585
  outputs[`${cppDir}/include/${spec.widgetName}Types.h`] = renderTypesHeader(spec, cppTypes);
2367
2586
  outputs[`${cppDir}/${spec.widgetName}.cpp`] = renderCppStub(spec, cppTypes);
2368
2587
  }
@@ -2374,38 +2593,13 @@ function generateOutputs(spec, options = { emitQWidget: true, emitAngularService
2374
2593
  return outputs;
2375
2594
  }
2376
2595
  function writeGeneratedOutputs(cwd, outputs) {
2377
- const outputRoot = node_path_1.default.join(cwd, "generated_output");
2596
+ const outputRoot = (0, layout_1.anqstGeneratedRootDir)(cwd);
2378
2597
  for (const [relPath, content] of Object.entries(outputs)) {
2379
2598
  const filePath = node_path_1.default.join(outputRoot, relPath);
2380
2599
  node_fs_1.default.mkdirSync(node_path_1.default.dirname(filePath), { recursive: true });
2381
2600
  node_fs_1.default.writeFileSync(filePath, withBuildStamp(relPath, content), "utf8");
2382
2601
  }
2383
2602
  }
2384
- function installTypeScriptOutputs(cwd) {
2385
- const sourceDir = node_path_1.default.join(cwd, "generated_output", "npmpackage");
2386
- const targetDir = node_path_1.default.join(cwd, "src", "anqst-generated");
2387
- if (!node_fs_1.default.existsSync(sourceDir))
2388
- return;
2389
- node_fs_1.default.rmSync(targetDir, { recursive: true, force: true });
2390
- node_fs_1.default.mkdirSync(targetDir, { recursive: true });
2391
- const queue = [sourceDir];
2392
- while (queue.length > 0) {
2393
- const current = queue.shift();
2394
- for (const entry of node_fs_1.default.readdirSync(current, { withFileTypes: true })) {
2395
- const abs = node_path_1.default.join(current, entry.name);
2396
- const rel = node_path_1.default.relative(sourceDir, abs);
2397
- const dst = node_path_1.default.join(targetDir, rel);
2398
- if (entry.isDirectory()) {
2399
- node_fs_1.default.mkdirSync(dst, { recursive: true });
2400
- queue.push(abs);
2401
- }
2402
- else if (entry.isFile()) {
2403
- node_fs_1.default.mkdirSync(node_path_1.default.dirname(dst), { recursive: true });
2404
- node_fs_1.default.copyFileSync(abs, dst);
2405
- }
2406
- }
2407
- }
2408
- }
2409
2603
  function listFilesRecursively(rootDir) {
2410
2604
  const output = [];
2411
2605
  const queue = [rootDir];
@@ -2539,7 +2733,7 @@ function installEmbeddedWebBundle(cwd, widgetName) {
2539
2733
  if (!node_fs_1.default.existsSync(node_path_1.default.join(distWebRoot, "index.html"))) {
2540
2734
  return false;
2541
2735
  }
2542
- const cppLibraryRoot = node_path_1.default.join(cwd, "generated_output", generatedCppLibraryDirName(widgetName));
2736
+ const cppLibraryRoot = (0, layout_1.resolveGeneratedLayoutPaths)(cwd, widgetName).cppQtWidgetRoot;
2543
2737
  const cppLibraryWebRoot = node_path_1.default.join(cppLibraryRoot, "webapp");
2544
2738
  node_fs_1.default.rmSync(cppLibraryWebRoot, { recursive: true, force: true });
2545
2739
  node_fs_1.default.mkdirSync(cppLibraryWebRoot, { recursive: true });
@@ -2571,15 +2765,15 @@ function normalizeEmbeddedIndexHtml(indexPath, webRoot) {
2571
2765
  node_fs_1.default.writeFileSync(indexPath, html, "utf8");
2572
2766
  }
2573
2767
  function renderQtIntegrationCMake(widgetName) {
2574
- const generatedRootVar = "ANQST_GENERATED_CPP_DIR";
2768
+ const generatedRootVar = "ANQST_GENERATED_WIDGET_DIR";
2575
2769
  const generatedIncludeVar = "ANQST_GENERATED_INCLUDE_DIR";
2576
- const webappRootVar = "ANQST_WEBAPP_ROOT";
2770
+ const projectRootVar = "ANQST_PROJECT_ROOT";
2577
2771
  const widgetTarget = `${widgetName}Widget`;
2578
2772
  const autogenTarget = `${widgetTarget}_anqst_codegen`;
2579
2773
  return `cmake_minimum_required(VERSION 3.21)
2580
2774
 
2581
- set(${webappRootVar} "\${CMAKE_CURRENT_LIST_DIR}/..")
2582
- set(${generatedRootVar} "\${${webappRootVar}}/generated_output/${generatedCppLibraryDirName(widgetName)}")
2775
+ set(${projectRootVar} "\${CMAKE_CURRENT_LIST_DIR}/../../../../..")
2776
+ set(${generatedRootVar} "\${CMAKE_CURRENT_LIST_DIR}/../qt/${generatedCppLibraryDirName(widgetName)}")
2583
2777
  set(${generatedIncludeVar} "\${${generatedRootVar}}/include")
2584
2778
 
2585
2779
  if(TARGET ${widgetTarget})
@@ -2587,7 +2781,7 @@ if(TARGET ${widgetTarget})
2587
2781
  endif()
2588
2782
 
2589
2783
  if(NOT TARGET anqstwebhostbase)
2590
- message(FATAL_ERROR "Target 'anqstwebhostbase' must exist before including anqst-cmake for ${widgetName}.")
2784
+ message(FATAL_ERROR "Target 'anqstwebhostbase' must exist before including generated AnQst CMake for ${widgetName}.")
2591
2785
  endif()
2592
2786
 
2593
2787
  find_package(Qt5 REQUIRED COMPONENTS Core Widgets)
@@ -2603,11 +2797,12 @@ add_custom_command(
2603
2797
  "\${${generatedRootVar}}/${widgetName}.qrc"
2604
2798
  "\${${generatedRootVar}}/${widgetName}.cpp"
2605
2799
  "\${${generatedIncludeVar}}/${widgetName}.h"
2800
+ "\${${generatedIncludeVar}}/${widgetName}Widget.h"
2606
2801
  "\${${generatedIncludeVar}}/${widgetName}Types.h"
2607
2802
  "\${${generatedRootVar}}/webapp/index.html"
2608
2803
  COMMAND "\${ANQST_NPM_EXECUTABLE}" install
2609
2804
  COMMAND "\${ANQST_NPM_EXECUTABLE}" run anqst:build
2610
- WORKING_DIRECTORY "\${${webappRootVar}}"
2805
+ WORKING_DIRECTORY "\${${projectRootVar}}"
2611
2806
  COMMENT "Generating AnQst widget library (${widgetTarget}) from Angular project"
2612
2807
  VERBATIM
2613
2808
  )
@@ -2618,6 +2813,7 @@ add_custom_target(${autogenTarget}
2618
2813
  "\${${generatedRootVar}}/${widgetName}.qrc"
2619
2814
  "\${${generatedRootVar}}/${widgetName}.cpp"
2620
2815
  "\${${generatedIncludeVar}}/${widgetName}.h"
2816
+ "\${${generatedIncludeVar}}/${widgetName}Widget.h"
2621
2817
  "\${${generatedIncludeVar}}/${widgetName}Types.h"
2622
2818
  "\${${generatedRootVar}}/webapp/index.html"
2623
2819
  )
@@ -2626,6 +2822,7 @@ set_source_files_properties(
2626
2822
  "\${${generatedRootVar}}/${widgetName}.qrc"
2627
2823
  "\${${generatedRootVar}}/${widgetName}.cpp"
2628
2824
  "\${${generatedIncludeVar}}/${widgetName}.h"
2825
+ "\${${generatedIncludeVar}}/${widgetName}Widget.h"
2629
2826
  "\${${generatedIncludeVar}}/${widgetName}Types.h"
2630
2827
  PROPERTIES GENERATED TRUE
2631
2828
  )
@@ -2634,6 +2831,7 @@ add_library(${widgetTarget}
2634
2831
  "\${${generatedRootVar}}/${widgetName}.qrc"
2635
2832
  "\${${generatedRootVar}}/${widgetName}.cpp"
2636
2833
  "\${${generatedIncludeVar}}/${widgetName}.h"
2834
+ "\${${generatedIncludeVar}}/${widgetName}Widget.h"
2637
2835
  "\${${generatedIncludeVar}}/${widgetName}Types.h"
2638
2836
  )
2639
2837
  add_dependencies(${widgetTarget} ${autogenTarget})
@@ -2648,9 +2846,9 @@ target_link_libraries(${widgetTarget}
2648
2846
  `;
2649
2847
  }
2650
2848
  function installQtIntegrationCMake(cwd, widgetName) {
2651
- const integrationDir = node_path_1.default.join(cwd, "anqst-cmake");
2849
+ const integrationDir = (0, layout_1.resolveGeneratedLayoutPaths)(cwd, widgetName).cppCmakeRoot;
2652
2850
  node_fs_1.default.mkdirSync(integrationDir, { recursive: true });
2653
- node_fs_1.default.writeFileSync(node_path_1.default.join(integrationDir, "CMakeLists.txt"), withBuildStamp("anqst-cmake/CMakeLists.txt", renderQtIntegrationCMake(widgetName)), "utf8");
2851
+ node_fs_1.default.writeFileSync(node_path_1.default.join(integrationDir, "CMakeLists.txt"), withBuildStamp("backend/cpp/cmake/CMakeLists.txt", renderQtIntegrationCMake(widgetName)), "utf8");
2654
2852
  }
2655
2853
  function normalizeIcoSize(dim) {
2656
2854
  return dim === 0 ? 256 : dim;
@@ -2821,7 +3019,7 @@ function installDesignerPluginIconAssets(cwd, pluginDir) {
2821
3019
  }
2822
3020
  function renderQtDesignerPluginCpp(widgetName, widgetCategory, hasIcon) {
2823
3021
  const pluginClass = `${widgetName}DesignerPlugin`;
2824
- const widgetClass = `${widgetName}::${widgetName}`;
3022
+ const widgetClass = `${widgetName}Widget`;
2825
3023
  const groupName = escapeCppStringLiteral(widgetCategory);
2826
3024
  const iconExpression = hasIcon
2827
3025
  ? 'QIcon(QStringLiteral(":/anqstdesignerplugin/plugin-icon.png"))'
@@ -2881,8 +3079,8 @@ set(CMAKE_AUTOMOC ON)
2881
3079
  set(CMAKE_AUTOUIC ON)
2882
3080
  set(CMAKE_AUTORCC ON)
2883
3081
 
2884
- set(ANQST_WEBAPP_ROOT "\${CMAKE_CURRENT_LIST_DIR}/../..")
2885
- set(ANQST_WIDGET_DIR "\${ANQST_WEBAPP_ROOT}/generated_output/${generatedCppLibraryDirName(widgetName)}")
3082
+ set(ANQST_PROJECT_ROOT "\${CMAKE_CURRENT_LIST_DIR}/../../../../../../..")
3083
+ set(ANQST_WIDGET_DIR "\${CMAKE_CURRENT_LIST_DIR}/..")
2886
3084
  set(ANQST_WEBBASE_DIR "" CACHE PATH "Path to AnQstWebBase source directory")
2887
3085
 
2888
3086
  if(NOT EXISTS "\${ANQST_WIDGET_DIR}/CMakeLists.txt")
@@ -2891,10 +3089,10 @@ endif()
2891
3089
 
2892
3090
  if(NOT ANQST_WEBBASE_DIR)
2893
3091
  foreach(candidate
2894
- "\${ANQST_WEBAPP_ROOT}/AnQstWidget/AnQstWebBase"
2895
- "\${ANQST_WEBAPP_ROOT}/../AnQstWidget/AnQstWebBase"
2896
- "\${ANQST_WEBAPP_ROOT}/../../AnQstWidget/AnQstWebBase"
2897
- "\${ANQST_WEBAPP_ROOT}/../../../AnQstWidget/AnQstWebBase")
3092
+ "\${ANQST_PROJECT_ROOT}/AnQstWidget/AnQstWebBase"
3093
+ "\${ANQST_PROJECT_ROOT}/../AnQstWidget/AnQstWebBase"
3094
+ "\${ANQST_PROJECT_ROOT}/../../AnQstWidget/AnQstWebBase"
3095
+ "\${ANQST_PROJECT_ROOT}/../../../AnQstWidget/AnQstWebBase")
2898
3096
  if(EXISTS "\${candidate}/CMakeLists.txt")
2899
3097
  set(ANQST_WEBBASE_DIR "\${candidate}")
2900
3098
  break()
@@ -2938,11 +3136,11 @@ set_target_properties(${pluginTarget} PROPERTIES
2938
3136
  `;
2939
3137
  }
2940
3138
  function installQtDesignerPluginCMake(cwd, widgetName, options = {}) {
2941
- const pluginDir = node_path_1.default.join(cwd, "anqst-cmake", "designerplugin");
3139
+ const pluginDir = (0, layout_1.resolveGeneratedLayoutPaths)(cwd, widgetName).designerPluginRoot;
2942
3140
  node_fs_1.default.mkdirSync(pluginDir, { recursive: true });
2943
3141
  const assets = installDesignerPluginIconAssets(cwd, pluginDir);
2944
3142
  const pluginTarget = `${widgetName}DesignerPlugin`;
2945
3143
  const widgetCategory = options.widgetCategory ?? "AnQst Widgets";
2946
- node_fs_1.default.writeFileSync(node_path_1.default.join(pluginDir, "CMakeLists.txt"), withBuildStamp("anqst-cmake/designerplugin/CMakeLists.txt", renderQtDesignerPluginCMake(widgetName, assets.hasIcon)), "utf8");
2947
- node_fs_1.default.writeFileSync(node_path_1.default.join(pluginDir, `${pluginTarget}.cpp`), withBuildStamp(`anqst-cmake/designerplugin/${pluginTarget}.cpp`, renderQtDesignerPluginCpp(widgetName, widgetCategory, assets.hasIcon)), "utf8");
3144
+ node_fs_1.default.writeFileSync(node_path_1.default.join(pluginDir, "CMakeLists.txt"), withBuildStamp(`backend/cpp/qt/${generatedCppLibraryDirName(widgetName)}/designerPlugin/CMakeLists.txt`, renderQtDesignerPluginCMake(widgetName, assets.hasIcon)), "utf8");
3145
+ node_fs_1.default.writeFileSync(node_path_1.default.join(pluginDir, `${pluginTarget}.cpp`), withBuildStamp(`backend/cpp/qt/${generatedCppLibraryDirName(widgetName)}/designerPlugin/${pluginTarget}.cpp`, renderQtDesignerPluginCpp(widgetName, widgetCategory, assets.hasIcon)), "utf8");
2948
3146
  }