@dusted/anqst 0.1.3 → 1.0.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.
@@ -2,4 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ANQST_BUILD_STAMP = void 0;
4
4
  // Generated by scripts/build-with-stamp.js. Do not edit by hand.
5
- exports.ANQST_BUILD_STAMP = "0.1.3/da5d2ee_clean_build_1/2026-03-11";
5
+ exports.ANQST_BUILD_STAMP = "1.0.0/7bd720a_dirty_build_1/2026-03-29";
package/dist/src/emit.js CHANGED
@@ -12,7 +12,6 @@ const node_fs_1 = __importDefault(require("node:fs"));
12
12
  const node_path_1 = __importDefault(require("node:path"));
13
13
  const typescript_1 = __importDefault(require("typescript"));
14
14
  const pngjs_1 = require("pngjs");
15
- const build_stamp_1 = require("./build-stamp");
16
15
  const layout_1 = require("./layout");
17
16
  function stripAnQstType(typeText) {
18
17
  return typeText
@@ -627,6 +626,27 @@ function buildCppTypeContext(spec) {
627
626
  }
628
627
  return normalizer.buildContext();
629
628
  }
629
+ function collectDragDropMimeConstants(spec) {
630
+ const seen = new Set();
631
+ const constants = [];
632
+ for (const service of spec.services) {
633
+ for (const member of service.members) {
634
+ if ((member.kind === "DropTarget" || member.kind === "HoverTarget") && member.payloadTypeText) {
635
+ const typeName = member.payloadTypeText.replace(/\s/g, "");
636
+ const key = `${service.name}-${typeName}`;
637
+ if (seen.has(key))
638
+ continue;
639
+ seen.add(key);
640
+ constants.push({
641
+ typeName,
642
+ serviceName: service.name,
643
+ mimeType: `application/anqst-dragdropevent_${service.name}-${typeName}`
644
+ });
645
+ }
646
+ }
647
+ }
648
+ return constants;
649
+ }
630
650
  function renderTypesHeader(spec, cppTypes) {
631
651
  const decls = cppTypes.orderedDecls.map(renderCppDecl).join("\n\n");
632
652
  const metatypes = cppTypes.structNames
@@ -635,6 +655,11 @@ function renderTypesHeader(spec, cppTypes) {
635
655
  `Q_DECLARE_METATYPE(QList<${spec.widgetName}::${name}>)`
636
656
  ])
637
657
  .join("\n");
658
+ const mimeConstants = collectDragDropMimeConstants(spec);
659
+ const mimeConstantLines = mimeConstants
660
+ .map((c) => `static constexpr const char* kDragDropMime_${c.typeName} = "${c.mimeType}";`)
661
+ .join("\n");
662
+ const mimeBlock = mimeConstantLines.length > 0 ? `\n${mimeConstantLines}\n` : "";
638
663
  return `#pragma once
639
664
  #include <QString>
640
665
  #include <QStringList>
@@ -647,7 +672,7 @@ function renderTypesHeader(spec, cppTypes) {
647
672
  namespace ${spec.widgetName} {
648
673
 
649
674
  ${decls}
650
-
675
+ ${mimeBlock}
651
676
  } // namespace ${spec.widgetName}
652
677
 
653
678
  ${metatypes}
@@ -710,6 +735,15 @@ function renderWidgetHeader(spec, cppTypes) {
710
735
  publicSlots.push(`void ${member.name}Slot(const ${cppType}& value);`);
711
736
  }
712
737
  }
738
+ else if (member.kind === "DropTarget" && member.payloadTypeText) {
739
+ const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
740
+ signals.push(`void ${member.name}(const ${cppType}& payload, double x, double y);`);
741
+ }
742
+ else if (member.kind === "HoverTarget" && member.payloadTypeText) {
743
+ const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
744
+ signals.push(`void ${member.name}(const ${cppType}& payload, double x, double y);`);
745
+ signals.push(`void ${member.name}Left();`);
746
+ }
713
747
  }
714
748
  }
715
749
  return `#pragma once
@@ -860,6 +894,45 @@ function renderCppStub(spec, cppTypes) {
860
894
  lines.push(` Q_UNUSED(kResourcesInitialized);`);
861
895
  lines.push(` registerGeneratedMetaTypes();`);
862
896
  lines.push(` installBridgeBindings();`);
897
+ for (const service of spec.services) {
898
+ for (const member of service.members) {
899
+ if (member.kind === "DropTarget" && member.payloadTypeText) {
900
+ const typeName = member.payloadTypeText.replace(/\s/g, "");
901
+ const mimeConst = `${spec.widgetName}::kDragDropMime_${typeName}`;
902
+ lines.push(` registerDropTarget(QStringLiteral("${service.name}"), QStringLiteral("${member.name}"), QString::fromUtf8(${mimeConst}));`);
903
+ }
904
+ else if (member.kind === "HoverTarget" && member.payloadTypeText) {
905
+ const typeName = member.payloadTypeText.replace(/\s/g, "");
906
+ const mimeConst = `${spec.widgetName}::kDragDropMime_${typeName}`;
907
+ lines.push(` registerHoverTarget(QStringLiteral("${service.name}"), QStringLiteral("${member.name}"), QString::fromUtf8(${mimeConst}), ${member.hoverThrottleMs});`);
908
+ }
909
+ }
910
+ }
911
+ for (const service of spec.services) {
912
+ for (const member of service.members) {
913
+ if (member.kind === "DropTarget" && member.payloadTypeText) {
914
+ const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
915
+ lines.push(` QObject::connect(this, &AnQstWebHostBase::anQstBridge_dropReceived, this, [this](const QString& service, const QString& member, const QVariant& payload, double x, double y) {`);
916
+ lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
917
+ lines.push(` emit ${member.name}(payload.value<${cppType}>(), x, y);`);
918
+ lines.push(` }`);
919
+ lines.push(` });`);
920
+ }
921
+ else if (member.kind === "HoverTarget" && member.payloadTypeText) {
922
+ const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
923
+ lines.push(` QObject::connect(this, &AnQstWebHostBase::anQstBridge_hoverUpdated, this, [this](const QString& service, const QString& member, const QVariant& payload, double x, double y) {`);
924
+ lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
925
+ lines.push(` emit ${member.name}(payload.value<${cppType}>(), x, y);`);
926
+ lines.push(` }`);
927
+ lines.push(` });`);
928
+ lines.push(` QObject::connect(this, &AnQstWebHostBase::anQstBridge_hoverLeft, this, [this](const QString& service, const QString& member) {`);
929
+ lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
930
+ lines.push(` emit ${member.name}Left();`);
931
+ lines.push(` }`);
932
+ lines.push(` });`);
933
+ }
934
+ }
935
+ }
863
936
  lines.push(` QObject::connect(this, &AnQstWebHostBase::onHostError, this, &${widgetClassName}::diagnosticsForwarded);`);
864
937
  lines.push(` const bool rootOk = setContentRoot(QString::fromUtf8(kBootstrapContentRoot));`);
865
938
  lines.push(` const bool bridgeOk = setBridgeObject(this, QString::fromUtf8(kBootstrapBridgeObject));`);
@@ -1007,13 +1080,7 @@ function renderCppStub(spec, cppTypes) {
1007
1080
  lines.push(` }`);
1008
1081
  }
1009
1082
  }
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(` };`);
1083
+ lines.push(` return QVariant();`);
1017
1084
  lines.push(`}`);
1018
1085
  lines.push("");
1019
1086
  lines.push(`void ${widgetClassName}::handleGeneratedEmitter(const QString& service, const QString& member, const QVariantList& args) {`);
@@ -1172,7 +1239,24 @@ function normalizeSlashes(value) {
1172
1239
  return value.split(node_path_1.default.sep).join("/");
1173
1240
  }
1174
1241
  function resolveActiveBuildStamp() {
1175
- return build_stamp_1.ANQST_BUILD_STAMP.trim();
1242
+ const fromEnv = process.env.ANQST_BUILD_STAMP?.trim();
1243
+ if (fromEnv && fromEnv.length > 0) {
1244
+ return fromEnv;
1245
+ }
1246
+ const activePath = node_path_1.default.resolve(__dirname, "..", "..", ".anqstgen-version-active.json");
1247
+ if (!node_fs_1.default.existsSync(activePath)) {
1248
+ return "";
1249
+ }
1250
+ try {
1251
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(activePath, "utf8"));
1252
+ if (typeof parsed.active === "string" && parsed.active.trim().length > 0) {
1253
+ return parsed.active.trim();
1254
+ }
1255
+ }
1256
+ catch {
1257
+ return "";
1258
+ }
1259
+ return "";
1176
1260
  }
1177
1261
  function withBuildStamp(relativePath, content) {
1178
1262
  const stamp = resolveActiveBuildStamp();
@@ -1315,6 +1399,19 @@ function renderTsService(spec, serviceName) {
1315
1399
  constructorBodyLines.push(` this._bridge.onOutput("${serviceName}", "${m.name}", (value) => this._${m.name}.set(value as ${tsType}));`);
1316
1400
  }
1317
1401
  }
1402
+ if (m.kind === "DropTarget" && m.payloadTypeText) {
1403
+ const tsType = mapTypeTextToTs(m.payloadTypeText);
1404
+ fieldLines.push(` private readonly _${m.name} = signal<{ payload: ${tsType}; x: number; y: number } | null>(null);`);
1405
+ 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) => this._${m.name}.set({ payload: payload as ${tsType}, x, y }));`);
1407
+ }
1408
+ if (m.kind === "HoverTarget" && m.payloadTypeText) {
1409
+ const tsType = mapTypeTextToTs(m.payloadTypeText);
1410
+ fieldLines.push(` private readonly _${m.name} = signal<{ payload: ${tsType}; x: number; y: number } | null>(null);`);
1411
+ 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) => this._${m.name}.set({ payload: payload as ${tsType}, x, y }));`);
1413
+ constructorBodyLines.push(` this._bridge.onHoverLeft("${serviceName}", "${m.name}", () => this._${m.name}.set(null));`);
1414
+ }
1318
1415
  }
1319
1416
  const constructorLines = [
1320
1417
  " constructor() {",
@@ -1366,6 +1463,14 @@ function renderTsServiceDts(spec, serviceName) {
1366
1463
  setMembers.push(` ${m.name}(value: ${tsType}): void;`);
1367
1464
  }
1368
1465
  }
1466
+ if (m.kind === "DropTarget" && m.payloadTypeText) {
1467
+ const tsType = mapTypeTextToTs(m.payloadTypeText);
1468
+ classMembers.push(` ${m.name}(): { payload: ${tsType}; x: number; y: number } | null;`);
1469
+ }
1470
+ if (m.kind === "HoverTarget" && m.payloadTypeText) {
1471
+ const tsType = mapTypeTextToTs(m.payloadTypeText);
1472
+ classMembers.push(` ${m.name}(): { payload: ${tsType}; x: number; y: number } | null;`);
1473
+ }
1369
1474
  }
1370
1475
  const setInterfaceDecl = setMembers.length > 0
1371
1476
  ? `export interface ${setInterfaceName} {\n${setMembers.join("\n")}\n}`
@@ -1396,6 +1501,9 @@ type SlotHandler = (...args: unknown[]) => unknown;
1396
1501
  type OutputHandler = (value: unknown) => void;
1397
1502
  type SlotInvocationListener = (requestId: string, service: string, member: string, args: unknown[]) => void;
1398
1503
  type OutputListener = (service: string, member: string, value: unknown) => void;
1504
+ type DropListener = (service: string, member: string, payload: unknown, x: number, y: number) => void;
1505
+ type HoverListener = (service: string, member: string, payload: unknown, x: number, y: number) => void;
1506
+ type HoverLeftListener = (service: string, member: string) => void;
1399
1507
 
1400
1508
  interface HostBridgeApi {
1401
1509
  anQstBridge_call(service: string, member: string, args: unknown[], callback: (result: unknown) => void): void;
@@ -1407,6 +1515,9 @@ interface HostBridgeApi {
1407
1515
  anQstBridge_slotInvocationRequested: {
1408
1516
  connect: (cb: (requestId: string, service: string, member: string, args: unknown[]) => void) => void;
1409
1517
  };
1518
+ anQstBridge_dropReceived: { connect: (cb: (service: string, member: string, payload: unknown, x: number, y: number) => void) => void };
1519
+ anQstBridge_hoverUpdated: { connect: (cb: (service: string, member: string, payload: unknown, x: number, y: number) => void) => void };
1520
+ anQstBridge_hoverLeft: { connect: (cb: (service: string, member: string) => void) => void };
1410
1521
  }
1411
1522
 
1412
1523
  interface QWebChannelCtor {
@@ -1424,6 +1535,9 @@ interface BridgeAdapter {
1424
1535
  resolveSlot(requestId: string, ok: boolean, payload: unknown, error: string): void;
1425
1536
  onOutput(handler: OutputListener): void;
1426
1537
  onSlotInvocation(handler: SlotInvocationListener): void;
1538
+ onDrop(handler: DropListener): void;
1539
+ onHover(handler: HoverListener): void;
1540
+ onHoverLeft(handler: HoverLeftListener): void;
1427
1541
  }
1428
1542
 
1429
1543
  function isBridgeCallError(value: unknown): value is {
@@ -1511,6 +1625,18 @@ class QtWebChannelAdapter implements BridgeAdapter {
1511
1625
  onSlotInvocation(handler: SlotInvocationListener): void {
1512
1626
  this.host.anQstBridge_slotInvocationRequested.connect(handler);
1513
1627
  }
1628
+
1629
+ onDrop(handler: DropListener): void {
1630
+ this.host.anQstBridge_dropReceived.connect(handler);
1631
+ }
1632
+
1633
+ onHover(handler: HoverListener): void {
1634
+ this.host.anQstBridge_hoverUpdated.connect(handler);
1635
+ }
1636
+
1637
+ onHoverLeft(handler: HoverLeftListener): void {
1638
+ this.host.anQstBridge_hoverLeft.connect(handler);
1639
+ }
1514
1640
  }
1515
1641
 
1516
1642
  class WebSocketBridgeAdapter implements BridgeAdapter {
@@ -1523,6 +1649,9 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
1523
1649
  }>();
1524
1650
  private readonly outputListeners: OutputListener[] = [];
1525
1651
  private readonly slotListeners: SlotInvocationListener[] = [];
1652
+ private readonly dropListeners: DropListener[] = [];
1653
+ private readonly hoverListeners: HoverListener[] = [];
1654
+ private readonly hoverLeftListeners: HoverLeftListener[] = [];
1526
1655
  private requestCounter = 0;
1527
1656
 
1528
1657
  private constructor(private readonly socket: WebSocket) {
@@ -1562,6 +1691,34 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
1562
1691
  }
1563
1692
  return;
1564
1693
  }
1694
+ if (type === "dropReceived") {
1695
+ const service = String(message["service"] ?? "");
1696
+ const member = String(message["member"] ?? "");
1697
+ const x = Number(message["x"] ?? 0);
1698
+ const y = Number(message["y"] ?? 0);
1699
+ for (const listener of this.dropListeners) {
1700
+ listener(service, member, message["payload"], x, y);
1701
+ }
1702
+ return;
1703
+ }
1704
+ if (type === "hoverUpdated") {
1705
+ const service = String(message["service"] ?? "");
1706
+ const member = String(message["member"] ?? "");
1707
+ const x = Number(message["x"] ?? 0);
1708
+ const y = Number(message["y"] ?? 0);
1709
+ for (const listener of this.hoverListeners) {
1710
+ listener(service, member, message["payload"], x, y);
1711
+ }
1712
+ return;
1713
+ }
1714
+ if (type === "hoverLeft") {
1715
+ const service = String(message["service"] ?? "");
1716
+ const member = String(message["member"] ?? "");
1717
+ for (const listener of this.hoverLeftListeners) {
1718
+ listener(service, member);
1719
+ }
1720
+ return;
1721
+ }
1565
1722
  if (type === "hostError") {
1566
1723
  console.error("AnQst host error:", message["payload"]);
1567
1724
  return;
@@ -1649,6 +1806,18 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
1649
1806
  onSlotInvocation(handler: SlotInvocationListener): void {
1650
1807
  this.slotListeners.push(handler);
1651
1808
  }
1809
+
1810
+ onDrop(handler: DropListener): void {
1811
+ this.dropListeners.push(handler);
1812
+ }
1813
+
1814
+ onHover(handler: HoverListener): void {
1815
+ this.hoverListeners.push(handler);
1816
+ }
1817
+
1818
+ onHoverLeft(handler: HoverLeftListener): void {
1819
+ this.hoverLeftListeners.push(handler);
1820
+ }
1652
1821
  }
1653
1822
 
1654
1823
  @Injectable({ providedIn: "root" })
@@ -1656,6 +1825,9 @@ class AnQstBridgeRuntime {
1656
1825
  private adapter: BridgeAdapter | null = null;
1657
1826
  private readonly slotHandlers = new Map<string, SlotHandler>();
1658
1827
  private readonly outputHandlers = new Map<string, OutputHandler[]>();
1828
+ private readonly dropHandlers = new Map<string, ((payload: unknown, x: number, y: number) => void)[]>();
1829
+ private readonly hoverHandlers = new Map<string, ((payload: unknown, x: number, y: number) => void)[]>();
1830
+ private readonly hoverLeftHandlers = new Map<string, (() => void)[]>();
1659
1831
  private readonly startup = this.init();
1660
1832
 
1661
1833
  async ready(): Promise<void> {
@@ -1706,6 +1878,27 @@ class AnQstBridgeRuntime {
1706
1878
  this.outputHandlers.set(key, existing);
1707
1879
  }
1708
1880
 
1881
+ onDrop(service: string, member: string, handler: (payload: unknown, x: number, y: number) => void): void {
1882
+ const key = this.key(service, member);
1883
+ const existing = this.dropHandlers.get(key) ?? [];
1884
+ existing.push(handler);
1885
+ this.dropHandlers.set(key, existing);
1886
+ }
1887
+
1888
+ onHover(service: string, member: string, handler: (payload: unknown, x: number, y: number) => void): void {
1889
+ const key = this.key(service, member);
1890
+ const existing = this.hoverHandlers.get(key) ?? [];
1891
+ existing.push(handler);
1892
+ this.hoverHandlers.set(key, existing);
1893
+ }
1894
+
1895
+ onHoverLeft(service: string, member: string, handler: () => void): void {
1896
+ const key = this.key(service, member);
1897
+ const existing = this.hoverLeftHandlers.get(key) ?? [];
1898
+ existing.push(handler);
1899
+ this.hoverLeftHandlers.set(key, existing);
1900
+ }
1901
+
1709
1902
  private requireAdapterSync(): BridgeAdapter {
1710
1903
  if (this.adapter === null) {
1711
1904
  throw new Error("AnQst bridge is not ready.");
@@ -1751,6 +1944,24 @@ class AnQstBridgeRuntime {
1751
1944
  this.adapter!.resolveSlot(requestId, false, undefined, message);
1752
1945
  }
1753
1946
  });
1947
+ this.adapter.onDrop((service, member, payload, x, y) => {
1948
+ const key = this.key(service, member);
1949
+ for (const handler of this.dropHandlers.get(key) ?? []) {
1950
+ handler(payload, x, y);
1951
+ }
1952
+ });
1953
+ this.adapter.onHover((service, member, payload, x, y) => {
1954
+ const key = this.key(service, member);
1955
+ for (const handler of this.hoverHandlers.get(key) ?? []) {
1956
+ handler(payload, x, y);
1957
+ }
1958
+ });
1959
+ this.adapter.onHoverLeft((service, member) => {
1960
+ const key = this.key(service, member);
1961
+ for (const handler of this.hoverLeftHandlers.get(key) ?? []) {
1962
+ handler();
1963
+ }
1964
+ });
1754
1965
  for (const key of this.slotHandlers.keys()) {
1755
1966
  const parts = key.split("::");
1756
1967
  if (parts.length === 2) {
@@ -2790,6 +3001,7 @@ set(CMAKE_AUTOUIC ON)
2790
3001
  set(CMAKE_AUTORCC ON)
2791
3002
 
2792
3003
  find_program(ANQST_NPM_EXECUTABLE npm REQUIRED)
3004
+ find_program(ANQST_NPX_EXECUTABLE npx REQUIRED)
2793
3005
 
2794
3006
  add_custom_command(
2795
3007
  OUTPUT
@@ -2801,7 +3013,7 @@ add_custom_command(
2801
3013
  "\${${generatedIncludeVar}}/${widgetName}Types.h"
2802
3014
  "\${${generatedRootVar}}/webapp/index.html"
2803
3015
  COMMAND "\${ANQST_NPM_EXECUTABLE}" install
2804
- COMMAND "\${ANQST_NPM_EXECUTABLE}" run anqst:build
3016
+ COMMAND "\${ANQST_NPX_EXECUTABLE}" anqst build
2805
3017
  WORKING_DIRECTORY "\${${projectRootVar}}"
2806
3018
  COMMENT "Generating AnQst widget library (${widgetTarget}) from Angular project"
2807
3019
  VERBATIM
@@ -3029,7 +3241,7 @@ function renderQtDesignerPluginCpp(widgetName, widgetCategory, hasIcon) {
3029
3241
  #include <QObject>
3030
3242
  #include <QString>
3031
3243
  #include <QWidget>
3032
- #include "include/${widgetName}.h"
3244
+ #include "${widgetName}.h"
3033
3245
 
3034
3246
  class ${pluginClass} final : public QObject, public QDesignerCustomWidgetInterface {
3035
3247
  Q_OBJECT
@@ -3045,9 +3257,10 @@ public:
3045
3257
  QString toolTip() const override { return QStringLiteral("${widgetName} generated by AnQst."); }
3046
3258
  QString whatsThis() const override { return QStringLiteral("${widgetName} generated by AnQst."); }
3047
3259
  bool isContainer() const override { return false; }
3048
- QString includeFile() const override { return QStringLiteral("include/${widgetName}.h"); }
3260
+ QString includeFile() const override { return QStringLiteral("${widgetName}.h"); }
3049
3261
  QWidget* createWidget(QWidget* parent) override {
3050
3262
  auto* widget = new ${widgetClass}(parent);
3263
+ widget->setMinimumHeight(128);
3051
3264
  widget->setProperty("anqstDesignerContext", true);
3052
3265
  return widget;
3053
3266
  }
@@ -3058,6 +3271,12 @@ public:
3058
3271
  return QStringLiteral(
3059
3272
  "<ui language=\\"c++\\">\\n"
3060
3273
  " <widget class=\\"${widgetClass}\\" name=\\"${widgetName.toLowerCase()}\\">\\n"
3274
+ " <property name=\\"minimumSize\\">\\n"
3275
+ " <size>\\n"
3276
+ " <width>256</width>\\n"
3277
+ " <height>128</height>\\n"
3278
+ " </size>\\n"
3279
+ " </property>\\n"
3061
3280
  " </widget>\\n"
3062
3281
  "</ui>\\n");
3063
3282
  }
@@ -55,7 +55,7 @@ function parseMemberKindFromAnQstType(typeNode) {
55
55
  if (!typeName.startsWith("AnQst."))
56
56
  return null;
57
57
  const kind = typeName.slice("AnQst.".length);
58
- if (!["Call", "Slot", "Emitter", "Output", "Input"].includes(kind))
58
+ if (!["Call", "Slot", "Emitter", "Output", "Input", "DropTarget", "HoverTarget"].includes(kind))
59
59
  return null;
60
60
  if (kind === "Emitter")
61
61
  return { kind, payload: null };
@@ -71,7 +71,7 @@ function parseMemberKindWithConfig(typeNode) {
71
71
  if (!typeName.startsWith("AnQst."))
72
72
  return null;
73
73
  const kind = typeName.slice("AnQst.".length);
74
- if (!["Call", "Slot", "Emitter", "Output", "Input"].includes(kind))
74
+ if (!["Call", "Slot", "Emitter", "Output", "Input", "DropTarget", "HoverTarget"].includes(kind))
75
75
  return null;
76
76
  const typeArgs = typeNode.typeArguments ?? [];
77
77
  if (kind === "Emitter") {
@@ -84,7 +84,7 @@ function parseMemberKindWithConfig(typeNode) {
84
84
  return {
85
85
  kind,
86
86
  payload: typeArgs[0] ? typeArgs[0].getText() : null,
87
- configTypeNode: kind === "Call" ? (typeArgs[1] ?? null) : null
87
+ configTypeNode: (kind === "Call" || kind === "HoverTarget") ? (typeArgs[1] ?? null) : null
88
88
  };
89
89
  }
90
90
  function parseNumericLiteralType(node) {
@@ -156,6 +156,51 @@ function resolveMemberTimeoutMs(source, serviceName, memberName, kind, configTyp
156
156
  }
157
157
  return effectiveMs;
158
158
  }
159
+ const DEFAULT_HOVER_RATE_HZ = 60;
160
+ const DEFAULT_HOVER_THROTTLE_MS = Math.round(1000 / DEFAULT_HOVER_RATE_HZ);
161
+ function resolveHoverThrottleMs(source, serviceName, memberName, kind, configTypeNode, warnings, memberLoc) {
162
+ if (kind !== "HoverTarget")
163
+ return 0;
164
+ if (!configTypeNode)
165
+ return DEFAULT_HOVER_THROTTLE_MS;
166
+ if (!typescript_1.default.isTypeLiteralNode(configTypeNode)) {
167
+ throw new errors_1.VerifyError(`HoverTarget config for '${memberName}' must be an inline object literal type.`, locFromNode(source, configTypeNode));
168
+ }
169
+ let maxRateHz = null;
170
+ const memberPath = `${serviceName}.${memberName}`;
171
+ for (const prop of configTypeNode.members) {
172
+ if (!typescript_1.default.isPropertySignature(prop) || !prop.name) {
173
+ throw new errors_1.VerifyError(`HoverTarget config for '${memberName}' only supports named properties.`, locFromNode(source, prop));
174
+ }
175
+ if (!typescript_1.default.isIdentifier(prop.name)) {
176
+ throw new errors_1.VerifyError(`HoverTarget config for '${memberName}' only supports identifier keys.`, locFromNode(source, prop.name));
177
+ }
178
+ const key = prop.name.text;
179
+ if (!prop.type) {
180
+ throw new errors_1.VerifyError(`HoverTarget config key '${key}' in '${memberName}' must declare a numeric literal value.`, locFromNode(source, prop));
181
+ }
182
+ if (key !== "maxRateHz") {
183
+ warnings.push({
184
+ severity: "warn",
185
+ message: `Unknown HoverTarget config key '${key}' ignored for '${memberPath}'.`,
186
+ loc: locFromNode(source, prop.name),
187
+ memberPath
188
+ });
189
+ continue;
190
+ }
191
+ const numericValue = parseNumericLiteralType(prop.type);
192
+ if (numericValue === null || !Number.isFinite(numericValue)) {
193
+ throw new errors_1.VerifyError(`HoverTarget config key '${key}' in '${memberName}' must be a numeric literal >= 0.`, locFromNode(source, prop.type));
194
+ }
195
+ if (numericValue < 0) {
196
+ throw new errors_1.VerifyError(`HoverTarget config key '${key}' in '${memberName}' must be >= 0.`, locFromNode(source, prop.type));
197
+ }
198
+ maxRateHz = numericValue;
199
+ }
200
+ if (maxRateHz === null)
201
+ return DEFAULT_HOVER_THROTTLE_MS;
202
+ return maxRateHz === 0 ? 0 : Math.round(1000 / maxRateHz);
203
+ }
159
204
  function parseServiceMember(source, serviceName, member, warnings) {
160
205
  if (typescript_1.default.isMethodSignature(member)) {
161
206
  if (member.questionToken)
@@ -166,7 +211,7 @@ function parseServiceMember(source, serviceName, member, warnings) {
166
211
  const parsed = parseMemberKindWithConfig(returnType);
167
212
  if (!parsed)
168
213
  throw new errors_1.VerifyError(`Unsupported service method return type '${returnType.getText()}'.`, locFromNode(source, member));
169
- if (parsed.kind === "Input" || parsed.kind === "Output") {
214
+ if (parsed.kind === "Input" || parsed.kind === "Output" || parsed.kind === "DropTarget" || parsed.kind === "HoverTarget") {
170
215
  throw new errors_1.VerifyError(`${parsed.kind} must be declared as property, not method.`, locFromNode(source, member));
171
216
  }
172
217
  if (parsed.kind === "Emitter" && parsed.configTypeNode !== null) {
@@ -191,6 +236,7 @@ function parseServiceMember(source, serviceName, member, warnings) {
191
236
  payloadTypeText: parsed.payload,
192
237
  parameters,
193
238
  timeoutMs,
239
+ hoverThrottleMs: 0,
194
240
  loc: locFromNode(source, member)
195
241
  };
196
242
  }
@@ -202,19 +248,21 @@ function parseServiceMember(source, serviceName, member, warnings) {
202
248
  const parsed = parseMemberKindWithConfig(member.type);
203
249
  if (!parsed)
204
250
  throw new errors_1.VerifyError(`Unsupported service property type '${member.type.getText()}'.`, locFromNode(source, member));
205
- if (parsed.kind !== "Input" && parsed.kind !== "Output") {
251
+ if (parsed.kind !== "Input" && parsed.kind !== "Output" && parsed.kind !== "DropTarget" && parsed.kind !== "HoverTarget") {
206
252
  throw new errors_1.VerifyError(`${parsed.kind} must be declared as method, not property.`, locFromNode(source, member));
207
253
  }
208
254
  if (!member.name || !typescript_1.default.isIdentifier(member.name)) {
209
255
  throw new errors_1.VerifyError("Only identifier service property names are supported.", locFromNode(source, member));
210
256
  }
211
257
  const timeoutMs = resolveMemberTimeoutMs(source, serviceName, member.name.text, parsed.kind, parsed.configTypeNode, warnings, locFromNode(source, member));
258
+ const hoverThrottleMs = resolveHoverThrottleMs(source, serviceName, member.name.text, parsed.kind, parsed.configTypeNode, warnings, locFromNode(source, member));
212
259
  return {
213
260
  kind: parsed.kind,
214
261
  name: member.name.text,
215
262
  payloadTypeText: parsed.payload,
216
263
  parameters: [],
217
264
  timeoutMs,
265
+ hoverThrottleMs,
218
266
  loc: locFromNode(source, member)
219
267
  };
220
268
  }
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@dusted/anqst",
3
- "version": "0.1.3",
3
+ "version": "1.0.0",
4
4
  "description": "Opinionated backend generator for webapps.",
5
5
  "keywords": [
6
6
  "nodejs",
7
7
  "qt",
8
8
  "angular",
9
- "webapp"
9
+ "webapp",
10
+ "apigenerator"
10
11
  ],
11
12
  "homepage": "https://github.com/DusteDdk/AnQst#readme",
12
13
  "bugs": {
@@ -1,249 +1,327 @@
1
- /**
2
- * AnQst-Spec Language - Canonical source of truth for the language.
3
- *
4
- * @remarks
5
- * - DSL for Widget generation, transported as TypeScript definition.
6
- * - For AnQSt Widget Specifications only.
7
- * - Exactly one toplevel namespace must be declared: It names the Widget.
8
- * - Service interfaces are optional. Namespace-local type declarations are valid generation roots.
9
- * - Imported/external types are generation-relevant when transitively reachable from namespace declarations.
10
- * - This is not an input file to the generator, it is the description of the language that describes the widget.
11
- * Not for use by TypeScript application implementation.
12
- * @example
13
- * package.json:
14
- * - "AnQst": "./AnQst/MyUserMgmtWidget.settings.json"
15
- */
16
-
17
- export namespace AnQst {
18
-
19
- /**
20
- * Declare service `InterfaceName`
21
- *
22
- * @remarks
23
- * Multiple allowed.
24
- * Affords developers of advanced widgets the ability to create domain-informed categories.
25
- * - Duplicate method declarations with identical parameter lists are invalid in normative AnQst-Spec input.
26
- *
27
- * @example
28
- * export interface UserService extends Widget.Service { }
29
- * // Generates UserService.
30
- */
31
- interface Service { }
32
-
33
- /**
34
- * Declare service `InterfaceName` as development-mode capable transport service.
35
- *
36
- * @remarks
37
- * - Extends the same method/property semantics as `AnQst.Service`.
38
- * - Signals to the generator/runtime that this widget should emit dual-transport bridge support
39
- * (Qt WebChannel + HTTP/WebSocket development bridge).
40
- * - Existing generated service APIs remain unchanged.
41
- *
42
- * @example
43
- * export interface UserService extends AnQst.AngularHTTPBaseServerClass { }
44
- */
45
- interface AngularHTTPBaseServerClass extends Service { }
46
-
47
- type CallConfig = { timeoutSeconds: number } | { timeoutMilliseconds: number } | {};
48
-
49
- /**
50
- * Declare non-blocking service method `MethodName`(`MethodArguments`): Promise<`MethodReturnType`>.
51
- * @remarks
52
- * - **Widget** -> Parent
53
- * - Flow:
54
- * - Widget: Call to service method `MethodName`(`MethodArguments`).
55
- * - Widget: Returns Promise<`MethodReturnType`>.
56
- * - Parent: Registered callback receives args and returns `T` synchronously.
57
- * - Widget: Promise resolves with payload `T` or rejects with plain error object.
58
- * - Optional config supports timeout tuning:
59
- * - `AnQst.Call<T, { timeoutSeconds: N }>`
60
- * - `AnQst.Call<T, { timeoutMilliseconds: N }>`
61
- * - Exactly one timeout key is allowed. Value must be integer >= 0.
62
- * - Default timeout is 120s. `0` means wait forever.
63
- * @example
64
- * // AnQst spec:
65
- * getUserById(userId: string): AnQst.Call<User>
66
- * //Angular app:
67
- * const user: User = await this.userService.getUserById("abc");
68
- */
69
- interface Call<T, Config extends CallConfig = {}> { dummy: T; config?: Config }
70
-
71
- /**
72
- * Declare blocking service method onSlot.`MethodName`( handler(`MethodArguments`):`MethodReturnType` ): void
73
- * @remarks
74
- * - **Parent** -> Widget
75
- * - Impl note: Autogenerated stub handler queues until handler is set (set method calls spools queue through handler)
76
- * - Flow:
77
- * - Parent: Call to generated widget method `MethodName`(`MethodArguments`).
78
- * - Widget: Registered handler(`MethodArguments`) is called.
79
- * - Widget: Handler return forms:
80
- * - `T` -> success payload
81
- * - `Promise<T>` -> awaited, success payload on resolve
82
- * - `Error` -> failure
83
- * - throw -> failure
84
- * - rejected promise -> failure
85
- * - Parent: `MethodName` call returns with result.
86
- * - Generated C++ Slot methods do not expose `ok/error` out parameters.
87
- * - Default Slot timeout is 1000ms.
88
- * Note: One active handler, calling will replace existing and is valid and allowed.
89
- * @example
90
- * // AnQst spec:
91
- * getUsernameSubstring(from: number, to:number ): AnQst.Slot<string>
92
- * //Angular app:
93
- * this.userService.onSlot.getUsername( provider );
94
- * //Parent:
95
- * auto currentFormUsername = userMgmt.getUsername();
96
- */
97
- interface Slot<T> { dummy: T }
98
-
99
-
100
-
101
- /**
102
- * Declare message emission method `MethodName`(`MethodArguments`): void
103
- * @remarks
104
- * - **Widget** -> ?Parent
105
- * True Qt signal semantics (Message is emitted, no return path, no registration requirement)
106
- * - Flow:
107
- * - Widget: Call to service method `MethodName`(`MethodArguments`).
108
- * - Widget: Returns void.
109
- * - Parent: Might have something connected to the signal, might not.
110
- * - If no listener is connected, event is dropped.
111
- * @example
112
- * // AnQst spec:
113
- * complain(whine: string): AnQst.Emitter;
114
- * //Angular app:
115
- * this.userService.complain("Why won't you LISTEN!");
116
- */
117
- interface Emitter { }
118
-
119
-
120
- /**
121
- * Declare reactive `PropertyName`:`OutputType` and set.`PropertyName`(arg: `OutputType`)
122
- * @remarks
123
- * - **Parent** -> Widget
124
- * True Angular signal semantics (Property updates, signal emits, no return path, no registration requirement)
125
- * - Flow:
126
- * - Parent: Sets generated widget property `PropertyName`.
127
- * - Widget: Service updates readonly property `PropertyName` and emits signal.
128
- * @example
129
- * // AnQst spec:
130
- * activeUsers: AnQst.Output<number>;
131
- * //Angular app template:
132
- * <p>{{ userService.activeUsers() }}</p>
133
- * //Parent:
134
- * int users = userMgmt.activeUsers;
135
- * userMgmt.activeUsers = 99;
136
- */
137
- interface Output<OutputType> {}
138
-
139
-
140
- /**
141
- * Declare Input bindable set.`PropertyName`(input:`InputType`): void and `PropertyName`:`InputType`
142
- * @remarks
143
- * - **Widget** -> Parent
144
- * - Convenience method, for symmetry with Output.
145
- * - Flow:
146
- * - Widget: Calls service set.`PropertyName`(`InputType`) to publish current value.
147
- * - Parent: Mirrored generated widget property `PropertyName` is updated and emits change notification.
148
- * @example
149
- * // AnQst spec:
150
- * currentUsername: AnQst.Input<string>;
151
- * //Angular app template:
152
- * <input type="text" placeholder="Your Name Here" (input)="userService.set.currentUsername(($event.target as HTMLInputElement).value)" />
153
- * //Parent:
154
- * QString userName = userMgmt.currentUsername;
155
- */
156
- interface Input<InputType> {}
157
-
158
- /**
159
- * AnQst-Spec type mapping overview and control.
160
- *
161
- * @remarks
162
- * Any Type that be mapped between TypeScript and Qt, C++ standard types or
163
- * Plain Old Data (POD) types (in that order of preference) will be mapped by default.
164
- *
165
- * Canonical mapping directive namespace is AnQst.Type.<type>.
166
- * To express advisory mapping preference, use AnQst.Type.<type>.
167
- * Advisory means generator SHOULD honor it, but MAY fall back to inferred/default mapping and emit diagnostic.
168
- * Array forms are equivalent: T[] == Array<T>, and AnQst.Type.X[] == Array<AnQst.Type.X>.
169
- *
170
- * TypeScript definitions+classes and C++ structs are generated for each
171
- * structured TypeScript type ( type = {...} or interface { ... } )
172
- * referenced in an AnQst spec.
173
- *
174
- */
175
- enum Type {
176
- object = "JavaScript Object <-> QVariantMap (JSON.stringify/parse semantics)",
177
- json = "JavaScript Object <-> QJsonObject (JSON.stringify/parse semantics)",
178
- string = "JavaScript String <-> QString",
179
- stringArray = "JavaScript String[] <-> QStringList",
180
- number = "JavaScript Number <-> double",
181
- qint64 = "JavaScript BigInt <-> qint64 (Default, for symmetry, same as direct use of <BigInt> which is allowed.)",
182
- quint64 = "JavaScript BigInt <-> quint64",
183
- qint32 = "JavaScript Number <-> qint32",
184
- quint32 = "JavaScript Number <-> quint32",
185
- qint16 = "JavaScript Number <-> qint16",
186
- quint16 = "JavaScript Number <-> quint16",
187
- qint8 = "JavaScript Number <-> qint8",
188
- quint8 = "JavaScript Number <-> quint8",
189
- int32 = "JavaScript Number <-> int32_t",
190
- uint32 = "JavaScript Number <-> uint32_t",
191
- int16 = "JavaScript Number <-> int16_t",
192
- uint16 = "JavaScript Number <-> uint16_t",
193
- int8 = "JavaScript Number <-> int8_t",
194
- uint8 = "JavaScript Number <-> uint8_t",
195
- buffer = "JavaScript ArrayBuffer <-> QByteArray (Default, for symmetry, same as direct use of <ArrayBuffer> which is allowed.)",
196
- blob = "JavaScript ArrayBuffer <-> QByteArray",
197
- typedArray = "JavaScript TypedArray <-> QByteArray",
198
- uint8Array = "JavaScript Uint8Array <-> QByteArray",
199
- int8Array = "JavaScript Int8Array <-> QByteArray",
200
- uint16Array = "JavaScript Uint16Array <-> QByteArray",
201
- int16Array = "JavaScript Int16Array <-> QByteArray",
202
- uint32Array = "JavaScript Uint32Array <-> QByteArray",
203
- int32Array = "JavaScript Int32Array <-> QByteArray",
204
- float32Array = "JavaScript Float32Array <-> QByteArray",
205
- float64Array = "JavaScript Float64Array <-> QByteArray",
206
- }
207
-
208
-
209
- /**
210
- * These are explicitly forbidden argument/return types.
211
- *
212
- * @remarks
213
- * - They may not be referenced in AnQst specs or imported types.
214
- * - Service methods cannot accept them as arguments
215
- * - Service methods cannot return them.
216
- * - Service properties of their type cannot be declared.
217
- *
218
- * - For Objects/Maps/Sets use AnQst.Type.<Type> instead.
219
- */
220
- export enum ForbiddenType {
221
- Function = "Passing callbacks across the boundary is not allowed.",
222
- Class = "Passing classes across the boundary is not allowed.",
223
- Type = "Passing types across the boundary is not allowed.",
224
- Promise = "Passing Promises across the boundary is not allowed",
225
- Callable = "Passing Callable objects across the boundary is not allowed",
226
- any = "Passing 'any' type across the boundary is not allowed",
227
- }
228
-
229
- /**
230
- * JS/TS Error instances can be returned, but they have special meaning.
231
- *
232
- * @remarks
233
- * AnQst is opinionated, Errors are not for control flow. They are for signalling unrecoverable and unhandled circumstance.
234
- * Use: To indicate unrecoverable program error/wrong use.
235
- * Don't use: To communicate expected and/or ignorable/handable situations, define a domain-specific transport type instad.
236
- * When else encountered: Unhandled Errors.
237
- * Effect: The receiving end throws an exception with message `<service>.<member> emitted error: <WidgetStackTrace>`
238
- * AnQst internal behavior:
239
- * When AnQst type translation/mapping encounters a true Error object instance, the Error object is not transported, instead
240
- * the sending AnQst code signals to the receiving AnQst code a message, and the receiving AnQst code throws a runtime exception on
241
- * the call or callback site in the Parent.
242
-
243
- */
244
- export enum ExceptionalType {
245
- Error = "AnQst will not transport an Error object, but will cause an exception to be thrown on reception site."
246
- }
247
-
248
- }
249
-
1
+ /**
2
+ * AnQst-Spec Language - Canonical source of truth for the language.
3
+ *
4
+ * @remarks
5
+ * - DSL for Widget generation, transported as TypeScript definition.
6
+ * - For AnQSt Widget Specifications only.
7
+ * - Exactly one toplevel namespace must be declared: It names the Widget.
8
+ * - Service interfaces are optional. Namespace-local type declarations are valid generation roots.
9
+ * - Imported/external types are generation-relevant when transitively reachable from namespace declarations.
10
+ * - This is not an input file to the generator, it is the description of the language that describes the widget.
11
+ * Not for use by TypeScript application implementation.
12
+ * @example
13
+ * package.json:
14
+ * - "AnQst": "./AnQst/MyUserMgmtWidget.settings.json"
15
+ */
16
+
17
+ export namespace AnQst {
18
+
19
+ /**
20
+ * Declare service `InterfaceName`
21
+ *
22
+ * @remarks
23
+ * Multiple allowed.
24
+ * Affords developers of advanced widgets the ability to create domain-informed categories.
25
+ * - Duplicate method declarations with identical parameter lists are invalid in normative AnQst-Spec input.
26
+ *
27
+ * @example
28
+ * export interface UserService extends Widget.Service { }
29
+ * // Generates UserService.
30
+ */
31
+ interface Service { }
32
+
33
+ /**
34
+ * Declare service `InterfaceName` as development-mode capable transport service.
35
+ *
36
+ * @remarks
37
+ * - Extends the same method/property semantics as `AnQst.Service`.
38
+ * - Signals to the generator/runtime that this widget should emit dual-transport bridge support
39
+ * (Qt WebChannel + HTTP/WebSocket development bridge).
40
+ * - Existing generated service APIs remain unchanged.
41
+ *
42
+ * @example
43
+ * export interface UserService extends AnQst.AngularHTTPBaseServerClass { }
44
+ */
45
+ interface AngularHTTPBaseServerClass extends Service { }
46
+
47
+ type CallConfig = { timeoutSeconds: number } | { timeoutMilliseconds: number } | {};
48
+
49
+ /**
50
+ * Declare non-blocking service method `MethodName`(`MethodArguments`): Promise<`MethodReturnType`>.
51
+ * @remarks
52
+ * - **Widget** -> Parent
53
+ * - Flow:
54
+ * - Widget: Call to service method `MethodName`(`MethodArguments`).
55
+ * - Widget: Returns Promise<`MethodReturnType`>.
56
+ * - Parent: Registered callback receives args and returns `T` synchronously.
57
+ * - Widget: Promise resolves with payload `T` or rejects with plain error object.
58
+ * - Optional config supports timeout tuning:
59
+ * - `AnQst.Call<T, { timeoutSeconds: N }>`
60
+ * - `AnQst.Call<T, { timeoutMilliseconds: N }>`
61
+ * - Exactly one timeout key is allowed. Value must be integer >= 0.
62
+ * - Default timeout is 120s. `0` means wait forever.
63
+ * @example
64
+ * // AnQst spec:
65
+ * getUserById(userId: string): AnQst.Call<User>
66
+ * //Angular app:
67
+ * const user: User = await this.userService.getUserById("abc");
68
+ */
69
+ interface Call<T, Config extends CallConfig = {}> { dummy: T; config?: Config }
70
+
71
+ /**
72
+ * Declare blocking service method onSlot.`MethodName`( handler(`MethodArguments`):`MethodReturnType` ): void
73
+ * @remarks
74
+ * - **Parent** -> Widget
75
+ * - Impl note: Autogenerated stub handler queues until handler is set (set method calls spools queue through handler)
76
+ * - Flow:
77
+ * - Parent: Call to generated widget method `MethodName`(`MethodArguments`).
78
+ * - Widget: Registered handler(`MethodArguments`) is called.
79
+ * - Widget: Handler return forms:
80
+ * - `T` -> success payload
81
+ * - `Promise<T>` -> awaited, success payload on resolve
82
+ * - `Error` -> failure
83
+ * - throw -> failure
84
+ * - rejected promise -> failure
85
+ * - Parent: `MethodName` call returns with result.
86
+ * - Default Slot timeout is 1000ms.
87
+ * Note: One active handler, calling will replace existing and is valid and allowed.
88
+ * @example
89
+ * // AnQst spec:
90
+ * getUsernameSubstring(from: number, to:number ): AnQst.Slot<string>
91
+ * //Angular app:
92
+ * this.userService.onSlot.getUsername( provider );
93
+ * //Parent:
94
+ * auto currentFormUsername = userMgmt.getUsername();
95
+ */
96
+ interface Slot<T> { dummy: T }
97
+
98
+
99
+
100
+ /**
101
+ * Declare message emission method `MethodName`(`MethodArguments`): void
102
+ * @remarks
103
+ * - **Widget** -> ?Parent
104
+ * True Qt signal semantics (Message is emitted, no return path, no registration requirement)
105
+ * - Flow:
106
+ * - Widget: Call to service method `MethodName`(`MethodArguments`).
107
+ * - Widget: Returns void.
108
+ * - Parent: Might have something connected to the signal, might not.
109
+ * - If no listener is connected, event is dropped.
110
+ * @example
111
+ * // AnQst spec:
112
+ * complain(whine: string): AnQst.Emitter;
113
+ * //Angular app:
114
+ * this.userService.complain("Why won't you LISTEN!");
115
+ */
116
+ interface Emitter { }
117
+
118
+
119
+ /**
120
+ * Declare reactive `PropertyName`:`OutputType` and set.`PropertyName`(arg: `OutputType`)
121
+ * @remarks
122
+ * - **Parent** -> Widget
123
+ * True Angular signal semantics (Property updates, signal emits, no return path, no registration requirement)
124
+ * - Flow:
125
+ * - Parent: Sets generated widget property `PropertyName`.
126
+ * - Widget: Service updates readonly property `PropertyName` and emits signal.
127
+ * @example
128
+ * // AnQst spec:
129
+ * activeUsers: AnQst.Output<number>;
130
+ * //Angular app template:
131
+ * <p>{{ userService.activeUsers() }}</p>
132
+ * //Parent:
133
+ * int users = userMgmt.activeUsers;
134
+ * userMgmt.activeUsers = 99;
135
+ */
136
+ interface Output<OutputType> {}
137
+
138
+
139
+ /**
140
+ * Declare Input bindable set.`PropertyName`(input:`InputType`): void and `PropertyName`:`InputType`
141
+ * @remarks
142
+ * - **Widget** -> Parent
143
+ * - Convenience method, for symmetry with Output.
144
+ * - Flow:
145
+ * - Widget: Calls service set.`PropertyName`(`InputType`) to publish current value.
146
+ * - Parent: Mirrored generated widget property `PropertyName` is updated and emits change notification.
147
+ * @example
148
+ * // AnQst spec:
149
+ * currentUsername: AnQst.Input<string>;
150
+ * //Angular app template:
151
+ * <input type="text" placeholder="Your Name Here" (input)="userService.set.currentUsername(($event.target as HTMLInputElement).value)" />
152
+ * //Parent:
153
+ * QString userName = userMgmt.currentUsername;
154
+ */
155
+ interface Input<InputType> {}
156
+
157
+
158
+ /**
159
+ * Declare drop-target `PropertyName`:`PayloadType`
160
+ * @remarks
161
+ * - **Parent** -> Widget (framework-mediated)
162
+ * - True Angular signal semantics.
163
+ * - Flow:
164
+ * - External: A Qt widget initiates a QDrag carrying QMimeData.
165
+ * - Parent: AnQstWebHostBase intercepts the drop via event filter on the
166
+ * embedded QWebEngineView's rendering surface.
167
+ * - Parent: QMimeData for the accepted format is deserialized (JSON) into
168
+ * the generated C++ struct and forwarded through the bridge.
169
+ * - Widget: Service updates signal `PropertyName` with the deserialized
170
+ * payload and drop coordinates. Angular components react via effect() / template binding.
171
+ * - MIME type is convention-derived: `application/anqst-dragdropevent_<ServiceName>-<TypeName>`.
172
+ * - The source QWidget must serialize the drag payload as JSON under the same MIME type.
173
+ * - Multiple DropTarget members per service are allowed (each accepting a different type).
174
+ * @example
175
+ * // AnQst spec:
176
+ * trackDropped: AnQst.DropTarget<Track>;
177
+ * // Angular app:
178
+ * effect(() => {
179
+ * const drop = this.service.trackDropped();
180
+ * if (drop !== null) { console.log(drop.payload, drop.x, drop.y); }
181
+ * });
182
+ */
183
+ interface DropTarget<T> { dummy: T }
184
+
185
+
186
+ /**
187
+ * Declare hover-target `PropertyName`:`PayloadType`
188
+ * @remarks
189
+ * - **Parent** -> Widget (framework-mediated)
190
+ * - True Angular signal semantics.
191
+ * - Flow:
192
+ * - External: A Qt widget initiates a QDrag carrying QMimeData.
193
+ * - Parent: AnQstWebHostBase intercepts drag-move events via event filter on the
194
+ * embedded QWebEngineView's rendering surface. Events are throttled (trailing edge).
195
+ * - Parent: Payload is deserialized once on DragEnter; subsequent DragMove events
196
+ * forward only the updated position.
197
+ * - Widget: Service updates signal `PropertyName` with the payload and current
198
+ * coordinates. Signal becomes null on DragLeave.
199
+ * - Shares the same MIME type convention as DropTarget: `application/anqst-dragdropevent_<ServiceName>-<TypeName>`.
200
+ * - A HoverTarget without a corresponding DropTarget means "show previews but reject the drop".
201
+ * - Optional config supports throttle tuning:
202
+ * - `AnQst.HoverTarget<T, { maxRateHz: N }>` — maximum rate of hover position updates
203
+ * forwarded across the bridge, in hertz. The generator converts this to a millisecond
204
+ * interval at build time (e.g. 60 Hz -> 17 ms, 10 Hz -> 100 ms).
205
+ * - Default is 60 Hz (~17 ms throttle interval).
206
+ * - `0` means no throttling: every QDragMoveEvent is forwarded immediately.
207
+ * - There is no upper bound on the value.
208
+ * @example
209
+ * // AnQst spec (default 60 Hz throttle):
210
+ * trackHovering: AnQst.HoverTarget<Track>;
211
+ * // AnQst spec (custom 10 Hz throttle):
212
+ * trackHovering: AnQst.HoverTarget<Track, { maxRateHz: 10 }>;
213
+ * // AnQst spec (no throttling):
214
+ * trackHovering: AnQst.HoverTarget<Track, { maxRateHz: 0 }>;
215
+ * // Angular app:
216
+ * effect(() => {
217
+ * const hover = this.service.trackHovering();
218
+ * if (hover !== null) { highlight(document.elementFromPoint(hover.x, hover.y)); }
219
+ * });
220
+ */
221
+
222
+ /**
223
+ * Configuration for HoverTarget throttle behavior.
224
+ * @remarks
225
+ * - `maxRateHz` Maximum rate in hertz at which hover position updates are forwarded.
226
+ * - Default (omitted or `{}`): 60 Hz.
227
+ * - `0`: No throttling; every QDragMoveEvent is forwarded.
228
+ * - No upper bound.
229
+ * - Value must be a numeric literal >= 0.
230
+ */
231
+ type HoverTargetConfig = { maxRateHz: number } | {};
232
+ interface HoverTarget<T, Config extends HoverTargetConfig = {}> { dummy: T; config?: Config }
233
+
234
+
235
+
236
+ /**
237
+ * AnQst-Spec type mapping overview and control.
238
+ *
239
+ * @remarks
240
+ * Any Type that be mapped between TypeScript and Qt, C++ standard types or
241
+ * Plain Old Data (POD) types (in that order of preference) will be mapped by default.
242
+ *
243
+ * Canonical mapping directive namespace is AnQst.Type.<type>.
244
+ * To express advisory mapping preference, use AnQst.Type.<type>.
245
+ * Advisory means generator SHOULD honor it, but MAY fall back to inferred/default mapping and emit diagnostic.
246
+ * Array forms are equivalent: T[] == Array<T>, and AnQst.Type.X[] == Array<AnQst.Type.X>.
247
+ *
248
+ * TypeScript definitions+classes and C++ structs are generated for each
249
+ * structured TypeScript type ( type = {...} or interface { ... } )
250
+ * referenced in an AnQst spec.
251
+ *
252
+ */
253
+ enum Type {
254
+ object = "JavaScript Object <-> QVariantMap (JSON.stringify/parse semantics)",
255
+ json = "JavaScript Object <-> QJsonObject (JSON.stringify/parse semantics)",
256
+ string = "JavaScript String <-> QString",
257
+ stringArray = "JavaScript String[] <-> QStringList",
258
+ number = "JavaScript Number <-> double",
259
+ qint64 = "JavaScript BigInt <-> qint64 (Default, for symmetry, same as direct use of <BigInt> which is allowed.)",
260
+ quint64 = "JavaScript BigInt <-> quint64",
261
+ qint32 = "JavaScript Number <-> qint32",
262
+ quint32 = "JavaScript Number <-> quint32",
263
+ qint16 = "JavaScript Number <-> qint16",
264
+ quint16 = "JavaScript Number <-> quint16",
265
+ qint8 = "JavaScript Number <-> qint8",
266
+ quint8 = "JavaScript Number <-> quint8",
267
+ int32 = "JavaScript Number <-> int32_t",
268
+ uint32 = "JavaScript Number <-> uint32_t",
269
+ int16 = "JavaScript Number <-> int16_t",
270
+ uint16 = "JavaScript Number <-> uint16_t",
271
+ int8 = "JavaScript Number <-> int8_t",
272
+ uint8 = "JavaScript Number <-> uint8_t",
273
+ buffer = "JavaScript ArrayBuffer <-> QByteArray (Default, for symmetry, same as direct use of <ArrayBuffer> which is allowed.)",
274
+ blob = "JavaScript ArrayBuffer <-> QByteArray",
275
+ typedArray = "JavaScript TypedArray <-> QByteArray",
276
+ uint8Array = "JavaScript Uint8Array <-> QByteArray",
277
+ int8Array = "JavaScript Int8Array <-> QByteArray",
278
+ uint16Array = "JavaScript Uint16Array <-> QByteArray",
279
+ int16Array = "JavaScript Int16Array <-> QByteArray",
280
+ uint32Array = "JavaScript Uint32Array <-> QByteArray",
281
+ int32Array = "JavaScript Int32Array <-> QByteArray",
282
+ float32Array = "JavaScript Float32Array <-> QByteArray",
283
+ float64Array = "JavaScript Float64Array <-> QByteArray",
284
+ }
285
+
286
+
287
+ /**
288
+ * These are explicitly forbidden argument/return types.
289
+ *
290
+ * @remarks
291
+ * - They may not be referenced in AnQst specs or imported types.
292
+ * - Service methods cannot accept them as arguments
293
+ * - Service methods cannot return them.
294
+ * - Service properties of their type cannot be declared.
295
+ *
296
+ * - For Objects/Maps/Sets use AnQst.Type.<Type> instead.
297
+ */
298
+ export enum ForbiddenType {
299
+ Function = "Passing callbacks across the boundary is not allowed.",
300
+ Class = "Passing classes across the boundary is not allowed.",
301
+ Type = "Passing types across the boundary is not allowed.",
302
+ Promise = "Passing Promises across the boundary is not allowed",
303
+ Callable = "Passing Callable objects across the boundary is not allowed",
304
+ any = "Passing 'any' type across the boundary is not allowed",
305
+ }
306
+
307
+ /**
308
+ * JS/TS Error instances can be returned, but they have special meaning.
309
+ *
310
+ * @remarks
311
+ * AnQst is opinionated, Errors are not for control flow. They are for signalling unrecoverable and unhandled circumstance.
312
+ * Use: To indicate unrecoverable program error/wrong use.
313
+ * Don't use: To communicate expected and/or ignorable/handable situations, define a domain-specific transport type instad.
314
+ * When else encountered: Unhandled Errors.
315
+ * Effect: The receiving end throws an exception with message `<service>.<member> emitted error: <WidgetStackTrace>`
316
+ * AnQst internal behavior:
317
+ * When AnQst type translation/mapping encounters a true Error object instance, the Error object is not transported, instead
318
+ * the sending AnQst code signals to the receiving AnQst code a message, and the receiving AnQst code throws a runtime exception on
319
+ * the call or callback site in the Parent.
320
+
321
+ */
322
+ export enum ExceptionalType {
323
+ Error = "AnQst will not transport an Error object, but will cause an exception to be thrown on reception site."
324
+ }
325
+
326
+ }
327
+