@dusted/anqst 1.5.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/src/app.js +31 -9
  2. package/dist/src/base93.js +0 -72
  3. package/dist/src/boundary-codec-analysis.js +468 -0
  4. package/dist/src/boundary-codec-leaves.js +602 -0
  5. package/dist/src/boundary-codec-model.js +77 -0
  6. package/dist/src/boundary-codec-plan.js +522 -0
  7. package/dist/src/boundary-codec-render.js +1738 -0
  8. package/dist/src/boundary-codecs.js +174 -0
  9. package/dist/src/emit.js +580 -90
  10. package/dist/src/program.js +1 -1
  11. package/package.json +2 -2
  12. package/dist/src/codecgenerators/basecodecemitters/bigint-qint64/decoder.js +0 -35
  13. package/dist/src/codecgenerators/basecodecemitters/bigint-qint64/encoder.js +0 -36
  14. package/dist/src/codecgenerators/basecodecemitters/bigint-quint64/decoder.js +0 -26
  15. package/dist/src/codecgenerators/basecodecemitters/bigint-quint64/encoder.js +0 -38
  16. package/dist/src/codecgenerators/basecodecemitters/binary-blob/decoder.js +0 -28
  17. package/dist/src/codecgenerators/basecodecemitters/binary-blob/encoder.js +0 -34
  18. package/dist/src/codecgenerators/basecodecemitters/binary-buffer/decoder.js +0 -29
  19. package/dist/src/codecgenerators/basecodecemitters/binary-buffer/encoder.js +0 -36
  20. package/dist/src/codecgenerators/basecodecemitters/binary-float32Array/decoder.js +0 -46
  21. package/dist/src/codecgenerators/basecodecemitters/binary-float32Array/encoder.js +0 -49
  22. package/dist/src/codecgenerators/basecodecemitters/binary-float64Array/decoder.js +0 -46
  23. package/dist/src/codecgenerators/basecodecemitters/binary-float64Array/encoder.js +0 -47
  24. package/dist/src/codecgenerators/basecodecemitters/binary-int16Array/decoder.js +0 -46
  25. package/dist/src/codecgenerators/basecodecemitters/binary-int16Array/encoder.js +0 -49
  26. package/dist/src/codecgenerators/basecodecemitters/binary-int32Array/decoder.js +0 -50
  27. package/dist/src/codecgenerators/basecodecemitters/binary-int32Array/encoder.js +0 -52
  28. package/dist/src/codecgenerators/basecodecemitters/binary-int8Array/decoder.js +0 -38
  29. package/dist/src/codecgenerators/basecodecemitters/binary-int8Array/encoder.js +0 -44
  30. package/dist/src/codecgenerators/basecodecemitters/binary-typedArray/decoder.js +0 -33
  31. package/dist/src/codecgenerators/basecodecemitters/binary-typedArray/encoder.js +0 -34
  32. package/dist/src/codecgenerators/basecodecemitters/binary-uint16Array/decoder.js +0 -46
  33. package/dist/src/codecgenerators/basecodecemitters/binary-uint16Array/encoder.js +0 -49
  34. package/dist/src/codecgenerators/basecodecemitters/binary-uint32Array/decoder.js +0 -46
  35. package/dist/src/codecgenerators/basecodecemitters/binary-uint32Array/encoder.js +0 -49
  36. package/dist/src/codecgenerators/basecodecemitters/binary-uint8Array/decoder.js +0 -28
  37. package/dist/src/codecgenerators/basecodecemitters/binary-uint8Array/encoder.js +0 -34
  38. package/dist/src/codecgenerators/basecodecemitters/boolean/decoder.js +0 -34
  39. package/dist/src/codecgenerators/basecodecemitters/boolean/encoder.js +0 -40
  40. package/dist/src/codecgenerators/basecodecemitters/dynamic-json/decoder.js +0 -43
  41. package/dist/src/codecgenerators/basecodecemitters/dynamic-json/encoder.js +0 -45
  42. package/dist/src/codecgenerators/basecodecemitters/dynamic-object/decoder.js +0 -44
  43. package/dist/src/codecgenerators/basecodecemitters/dynamic-object/encoder.js +0 -46
  44. package/dist/src/codecgenerators/basecodecemitters/integer-int16/decoder.js +0 -32
  45. package/dist/src/codecgenerators/basecodecemitters/integer-int16/encoder.js +0 -43
  46. package/dist/src/codecgenerators/basecodecemitters/integer-int32/decoder.js +0 -26
  47. package/dist/src/codecgenerators/basecodecemitters/integer-int32/encoder.js +0 -37
  48. package/dist/src/codecgenerators/basecodecemitters/integer-int8/decoder.js +0 -26
  49. package/dist/src/codecgenerators/basecodecemitters/integer-int8/encoder.js +0 -37
  50. package/dist/src/codecgenerators/basecodecemitters/integer-qint16/decoder.js +0 -36
  51. package/dist/src/codecgenerators/basecodecemitters/integer-qint16/encoder.js +0 -36
  52. package/dist/src/codecgenerators/basecodecemitters/integer-qint32/decoder.js +0 -25
  53. package/dist/src/codecgenerators/basecodecemitters/integer-qint32/encoder.js +0 -36
  54. package/dist/src/codecgenerators/basecodecemitters/integer-qint8/decoder.js +0 -36
  55. package/dist/src/codecgenerators/basecodecemitters/integer-qint8/encoder.js +0 -36
  56. package/dist/src/codecgenerators/basecodecemitters/integer-quint16/decoder.js +0 -26
  57. package/dist/src/codecgenerators/basecodecemitters/integer-quint16/encoder.js +0 -38
  58. package/dist/src/codecgenerators/basecodecemitters/integer-quint32/decoder.js +0 -27
  59. package/dist/src/codecgenerators/basecodecemitters/integer-quint32/encoder.js +0 -39
  60. package/dist/src/codecgenerators/basecodecemitters/integer-quint8/decoder.js +0 -26
  61. package/dist/src/codecgenerators/basecodecemitters/integer-quint8/encoder.js +0 -38
  62. package/dist/src/codecgenerators/basecodecemitters/integer-uint16/decoder.js +0 -30
  63. package/dist/src/codecgenerators/basecodecemitters/integer-uint16/encoder.js +0 -42
  64. package/dist/src/codecgenerators/basecodecemitters/integer-uint32/decoder.js +0 -31
  65. package/dist/src/codecgenerators/basecodecemitters/integer-uint32/encoder.js +0 -43
  66. package/dist/src/codecgenerators/basecodecemitters/integer-uint8/decoder.js +0 -30
  67. package/dist/src/codecgenerators/basecodecemitters/integer-uint8/encoder.js +0 -40
  68. package/dist/src/codecgenerators/basecodecemitters/number/decoder.js +0 -26
  69. package/dist/src/codecgenerators/basecodecemitters/number/encoder.js +0 -38
  70. package/dist/src/codecgenerators/basecodecemitters/shared/comments.js +0 -13
  71. package/dist/src/codecgenerators/basecodecemitters/shared/contracts.js +0 -2
  72. package/dist/src/codecgenerators/basecodecemitters/shared/fixedwidth.js +0 -53
  73. package/dist/src/codecgenerators/basecodecemitters/shared/index.js +0 -21
  74. package/dist/src/codecgenerators/basecodecemitters/shared/positionalBase93.js +0 -48
  75. package/dist/src/codecgenerators/basecodecemitters/shared/rawbytes.js +0 -30
  76. package/dist/src/codecgenerators/basecodecemitters/string/decoder.js +0 -43
  77. package/dist/src/codecgenerators/basecodecemitters/string/encoder.js +0 -43
  78. package/dist/src/codecgenerators/basecodecemitters/stringArray/decoder.js +0 -80
  79. package/dist/src/codecgenerators/basecodecemitters/stringArray/encoder.js +0 -57
  80. package/dist/src/structured-top-level-codecs.js +0 -1305
package/dist/src/emit.js CHANGED
@@ -13,7 +13,7 @@ const node_path_1 = __importDefault(require("node:path"));
13
13
  const typescript_1 = __importDefault(require("typescript"));
14
14
  const pngjs_1 = require("pngjs");
15
15
  const layout_1 = require("./layout");
16
- const structured_top_level_codecs_1 = require("./structured-top-level-codecs");
16
+ const boundary_codecs_1 = require("./boundary-codecs");
17
17
  function stripAnQstType(typeText) {
18
18
  return typeText
19
19
  .replace(/\bAnQst\.Type\.stringArray\b/g, "string[]")
@@ -83,6 +83,99 @@ function isNumberLikeUnionTypeNode(node) {
83
83
  return false;
84
84
  });
85
85
  }
86
+ function collectFiniteStringLiteralsTypeNode(node) {
87
+ const values = [];
88
+ for (const part of node.types) {
89
+ if (!typescript_1.default.isLiteralTypeNode(part) || !typescript_1.default.isStringLiteral(part.literal))
90
+ return null;
91
+ values.push(part.literal.text);
92
+ }
93
+ return values;
94
+ }
95
+ function collectFiniteBooleanLiteralsTypeNode(node) {
96
+ const values = [];
97
+ for (const part of node.types) {
98
+ if (!typescript_1.default.isLiteralTypeNode(part))
99
+ return null;
100
+ if (part.literal.kind === typescript_1.default.SyntaxKind.TrueKeyword) {
101
+ values.push(true);
102
+ continue;
103
+ }
104
+ if (part.literal.kind === typescript_1.default.SyntaxKind.FalseKeyword) {
105
+ values.push(false);
106
+ continue;
107
+ }
108
+ return null;
109
+ }
110
+ return values;
111
+ }
112
+ function collectFiniteNumberLiteralsTypeNode(node) {
113
+ const values = [];
114
+ for (const part of node.types) {
115
+ if (!typescript_1.default.isLiteralTypeNode(part) || !typescript_1.default.isNumericLiteral(part.literal))
116
+ return null;
117
+ values.push(Number(part.literal.text));
118
+ }
119
+ return values;
120
+ }
121
+ function finiteDomainSymbolForCpp(value) {
122
+ if (typeof value === "boolean")
123
+ return value ? "True" : "False";
124
+ if (typeof value === "number") {
125
+ const text = Number.isInteger(value) ? `${value}` : `${value}`.replace(/\./g, "_");
126
+ return sanitizeIdentifier(`Value_${text.replace(/-/g, "neg_")}`);
127
+ }
128
+ const direct = sanitizeIdentifier(value.trim());
129
+ return direct.length > 0 ? direct : "Value";
130
+ }
131
+ function buildCppFiniteDomain(primitive, values) {
132
+ const seen = new Set();
133
+ const variants = [];
134
+ for (const value of values) {
135
+ const key = `${typeof value}:${String(value)}`;
136
+ if (seen.has(key))
137
+ continue;
138
+ seen.add(key);
139
+ variants.push({
140
+ code: variants.length,
141
+ symbolicName: finiteDomainSymbolForCpp(value),
142
+ value
143
+ });
144
+ }
145
+ return { primitive, variants };
146
+ }
147
+ function collectFiniteDomainFromTypeNode(typeNode) {
148
+ if (typescript_1.default.isParenthesizedTypeNode(typeNode)) {
149
+ return collectFiniteDomainFromTypeNode(typeNode.type);
150
+ }
151
+ if (typescript_1.default.isLiteralTypeNode(typeNode)) {
152
+ if (typescript_1.default.isStringLiteral(typeNode.literal)) {
153
+ return buildCppFiniteDomain("string", [typeNode.literal.text]);
154
+ }
155
+ if (typescript_1.default.isNumericLiteral(typeNode.literal)) {
156
+ return buildCppFiniteDomain("number", [Number(typeNode.literal.text)]);
157
+ }
158
+ if (typeNode.literal.kind === typescript_1.default.SyntaxKind.TrueKeyword || typeNode.literal.kind === typescript_1.default.SyntaxKind.FalseKeyword) {
159
+ return buildCppFiniteDomain("boolean", [typeNode.literal.kind === typescript_1.default.SyntaxKind.TrueKeyword]);
160
+ }
161
+ return null;
162
+ }
163
+ if (!typescript_1.default.isUnionTypeNode(typeNode))
164
+ return null;
165
+ const filtered = filterNullishUnionTypeNodes(typeNode.types);
166
+ if (filtered.length !== typeNode.types.length)
167
+ return null;
168
+ const finiteStrings = collectFiniteStringLiteralsTypeNode(typeNode);
169
+ if (finiteStrings)
170
+ return buildCppFiniteDomain("string", finiteStrings);
171
+ const finiteBooleans = collectFiniteBooleanLiteralsTypeNode(typeNode);
172
+ if (finiteBooleans)
173
+ return buildCppFiniteDomain("boolean", finiteBooleans);
174
+ const finiteNumbers = collectFiniteNumberLiteralsTypeNode(typeNode);
175
+ if (finiteNumbers)
176
+ return buildCppFiniteDomain("number", finiteNumbers);
177
+ return null;
178
+ }
86
179
  function mapTsTypeToCpp(typeText) {
87
180
  const raw = typeText.trim();
88
181
  if (/\bAnQst\.Type\.qint64\b/.test(raw))
@@ -179,6 +272,11 @@ function callbackName(memberName) {
179
272
  function pascalCase(value) {
180
273
  return value.length === 0 ? value : `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
181
274
  }
275
+ function sanitizeIdentifier(value) {
276
+ const trimmed = value.replace(/[^A-Za-z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
277
+ const withFallback = trimmed.length > 0 ? trimmed : "Codec";
278
+ return /^[0-9]/.test(withFallback) ? `T_${withFallback}` : withFallback;
279
+ }
182
280
  function variantToCppExpression(cppType, expr) {
183
281
  if (cppType === "QString")
184
282
  return `${expr}.toString()`;
@@ -471,6 +569,7 @@ class CppTypeNormalizer {
471
569
  this.allKnownNames = new Set();
472
570
  this.usedNames = new Set();
473
571
  this.syntheticNameByKey = new Map();
572
+ this.finiteDomainNameByKey = new Map();
474
573
  for (const decl of collectStructDecls(spec)) {
475
574
  this.allKnownNames.add(decl.name);
476
575
  this.usedNames.add(decl.name);
@@ -494,9 +593,11 @@ class CppTypeNormalizer {
494
593
  const order = this.topologicalOrder();
495
594
  const orderedDecls = order.map((name) => this.declMap.get(name)).filter((x) => !!x);
496
595
  const structNames = orderedDecls.filter((d) => d.kind === "struct").map((d) => d.name);
596
+ const metatypeNames = orderedDecls.filter((d) => d.kind === "struct" || d.kind === "enum").map((d) => d.name);
497
597
  return {
498
598
  orderedDecls,
499
599
  structNames,
600
+ metatypeNames,
500
601
  mapTypeText: (typeText, nameHintParts) => this.mapTypeText(typeText, nameHintParts)
501
602
  };
502
603
  }
@@ -514,7 +615,19 @@ class CppTypeNormalizer {
514
615
  optional: !!member.questionToken
515
616
  });
516
617
  }
517
- return { name, kind: "struct", fields, aliasType: null, deps, isUnionAlias: false };
618
+ return { name, kind: "struct", fields, aliasType: null, deps, isUnionAlias: false, finiteDomain: null };
619
+ }
620
+ const finiteDomain = collectFiniteDomainFromTypeNode(node.type);
621
+ if (finiteDomain) {
622
+ return {
623
+ name,
624
+ kind: "enum",
625
+ fields: [],
626
+ aliasType: null,
627
+ deps: new Set(),
628
+ isUnionAlias: false,
629
+ finiteDomain
630
+ };
518
631
  }
519
632
  const deps = new Set();
520
633
  const aliasType = this.mapTypeNode(node.type, [name], deps);
@@ -524,13 +637,18 @@ class CppTypeNormalizer {
524
637
  fields: [],
525
638
  aliasType,
526
639
  deps,
527
- isUnionAlias: node.type.getText().includes("|")
640
+ isUnionAlias: node.type.getText().includes("|"),
641
+ finiteDomain: null
528
642
  };
529
643
  }
530
644
  mapTypeNode(typeNode, nameHintParts, deps) {
531
645
  if (typescript_1.default.isParenthesizedTypeNode(typeNode)) {
532
646
  return this.mapTypeNode(typeNode.type, nameHintParts, deps);
533
647
  }
648
+ const finiteDomain = collectFiniteDomainFromTypeNode(typeNode);
649
+ if (finiteDomain) {
650
+ return this.ensureFiniteDomainType(finiteDomain, nameHintParts, deps);
651
+ }
534
652
  if (typescript_1.default.isUnionTypeNode(typeNode)) {
535
653
  const filtered = filterNullishUnionTypeNodes(typeNode.types);
536
654
  if (filtered.length === 1) {
@@ -587,6 +705,34 @@ class CppTypeNormalizer {
587
705
  this.collectKnownTypeDeps(mapped, deps);
588
706
  return mapped;
589
707
  }
708
+ ensureFiniteDomainType(domain, nameHintParts, deps) {
709
+ const baseName = this.makeSyntheticBaseName(nameHintParts);
710
+ const domainKey = `${baseName}::finite::${domain.primitive}::${domain.variants.map((variant) => `${variant.symbolicName}=${String(variant.value)}`).join("|")}`;
711
+ const existingName = this.finiteDomainNameByKey.get(domainKey);
712
+ if (existingName) {
713
+ deps.add(existingName);
714
+ return existingName;
715
+ }
716
+ const synthesizedName = this.allocateUniqueName(baseName);
717
+ this.finiteDomainNameByKey.set(domainKey, synthesizedName);
718
+ if (this.declMap.has(synthesizedName)) {
719
+ deps.add(synthesizedName);
720
+ return synthesizedName;
721
+ }
722
+ this.allKnownNames.add(synthesizedName);
723
+ this.declMap.set(synthesizedName, {
724
+ name: synthesizedName,
725
+ kind: "enum",
726
+ fields: [],
727
+ aliasType: null,
728
+ deps: new Set(),
729
+ isUnionAlias: false,
730
+ finiteDomain: domain
731
+ });
732
+ this.seedOrder.push(synthesizedName);
733
+ deps.add(synthesizedName);
734
+ return synthesizedName;
735
+ }
590
736
  ensureSyntheticStruct(typeNode, nameHintParts, deps) {
591
737
  const baseName = this.makeSyntheticBaseName(nameHintParts);
592
738
  const syntheticKey = `${baseName}::${typeNode.getText()}`;
@@ -620,7 +766,8 @@ class CppTypeNormalizer {
620
766
  fields,
621
767
  aliasType: null,
622
768
  deps: localDeps,
623
- isUnionAlias: false
769
+ isUnionAlias: false,
770
+ finiteDomain: null
624
771
  });
625
772
  this.seedOrder.push(synthesizedName);
626
773
  deps.add(synthesizedName);
@@ -682,6 +829,17 @@ class CppTypeNormalizer {
682
829
  }
683
830
  }
684
831
  function renderCppDecl(decl) {
832
+ if (decl.kind === "enum") {
833
+ const variants = decl.finiteDomain?.variants ?? [];
834
+ const underlyingType = variants.length <= 0xff ? "std::uint8_t" : variants.length <= 0xffff ? "std::uint16_t" : "std::uint32_t";
835
+ const lines = [];
836
+ lines.push(`enum class ${decl.name} : ${underlyingType} {`);
837
+ for (const variant of variants) {
838
+ lines.push(` ${variant.symbolicName} = ${variant.code},`);
839
+ }
840
+ lines.push("};");
841
+ return lines.join("\n");
842
+ }
685
843
  if (decl.kind === "alias") {
686
844
  if (decl.isUnionAlias && decl.aliasType === "QString") {
687
845
  return `using ${decl.name} = QString; // union mapped conservatively`;
@@ -737,6 +895,146 @@ function collectDragDropMimeConstants(spec) {
737
895
  }
738
896
  return constants;
739
897
  }
898
+ function createCarrierSummary(counts, singleKinds = [], mayBlob = false, mustBlob = false) {
899
+ return {
900
+ counts: new Set(counts),
901
+ singleKinds: new Set(singleKinds),
902
+ mayBlob,
903
+ mustBlob
904
+ };
905
+ }
906
+ function addOptionalAbsence(summary) {
907
+ const counts = new Set(summary.counts);
908
+ counts.add(0);
909
+ return {
910
+ counts,
911
+ singleKinds: new Set(summary.singleKinds),
912
+ mayBlob: summary.mayBlob,
913
+ mustBlob: summary.mustBlob
914
+ };
915
+ }
916
+ function saturatingItemCountAdd(left, right) {
917
+ if (left === 2 || right === 2)
918
+ return 2;
919
+ const total = left + right;
920
+ return total >= 2 ? 2 : total;
921
+ }
922
+ function mergeCarrierSummaries(left, right) {
923
+ const counts = new Set();
924
+ const singleKinds = new Set();
925
+ for (const leftCount of left.counts) {
926
+ for (const rightCount of right.counts) {
927
+ const total = saturatingItemCountAdd(leftCount, rightCount);
928
+ counts.add(total);
929
+ if (total !== 1)
930
+ continue;
931
+ if (leftCount === 1 && rightCount === 0) {
932
+ for (const kind of left.singleKinds)
933
+ singleKinds.add(kind);
934
+ }
935
+ if (leftCount === 0 && rightCount === 1) {
936
+ for (const kind of right.singleKinds)
937
+ singleKinds.add(kind);
938
+ }
939
+ }
940
+ }
941
+ return {
942
+ counts,
943
+ singleKinds,
944
+ mayBlob: left.mayBlob || right.mayBlob,
945
+ mustBlob: left.mustBlob || right.mustBlob
946
+ };
947
+ }
948
+ function summarizePlanCarrier(node) {
949
+ switch (node.nodeKind) {
950
+ case "leaf":
951
+ if (node.blobEntryId)
952
+ return createCarrierSummary([0], [], true, true);
953
+ if (node.itemEntryId) {
954
+ return createCarrierSummary([1], [node.leaf.region === "dynamic" ? "object" : "string"]);
955
+ }
956
+ return createCarrierSummary([0]);
957
+ case "named":
958
+ return summarizePlanCarrier(node.target);
959
+ case "finite-domain":
960
+ if (node.blobEntryId)
961
+ return createCarrierSummary([0], [], true, true);
962
+ if (node.itemEntryId)
963
+ return createCarrierSummary([1], ["string"]);
964
+ return createCarrierSummary([0]);
965
+ case "array": {
966
+ if (node.extentStrategy === "blob-tail") {
967
+ return createCarrierSummary([0], [], true, false);
968
+ }
969
+ const elementSummary = summarizePlanCarrier(node.element);
970
+ const counts = new Set([0]);
971
+ const singleKinds = new Set();
972
+ if (elementSummary.counts.has(1)) {
973
+ counts.add(1);
974
+ for (const kind of elementSummary.singleKinds)
975
+ singleKinds.add(kind);
976
+ }
977
+ if (elementSummary.counts.has(1) || elementSummary.counts.has(2)) {
978
+ counts.add(2);
979
+ }
980
+ return {
981
+ counts,
982
+ singleKinds,
983
+ mayBlob: true,
984
+ mustBlob: true
985
+ };
986
+ }
987
+ case "struct": {
988
+ let summary = createCarrierSummary([0]);
989
+ for (const field of node.fields) {
990
+ let fieldSummary = summarizePlanCarrier(field.node);
991
+ if (field.optional) {
992
+ fieldSummary = addOptionalAbsence(fieldSummary);
993
+ }
994
+ if (field.presenceStrategy) {
995
+ fieldSummary = {
996
+ counts: new Set(fieldSummary.counts),
997
+ singleKinds: new Set(fieldSummary.singleKinds),
998
+ mayBlob: true,
999
+ mustBlob: true
1000
+ };
1001
+ }
1002
+ summary = mergeCarrierSummaries(summary, fieldSummary);
1003
+ }
1004
+ return summary;
1005
+ }
1006
+ }
1007
+ }
1008
+ function inferDragDropCarrierKinds(plan) {
1009
+ const summary = summarizePlanCarrier(plan.root);
1010
+ const carriers = new Set();
1011
+ const addNoBlobCarriers = () => {
1012
+ if (summary.counts.has(0))
1013
+ carriers.add("array");
1014
+ if (summary.counts.has(1)) {
1015
+ for (const kind of summary.singleKinds)
1016
+ carriers.add(kind);
1017
+ }
1018
+ if (summary.counts.has(2))
1019
+ carriers.add("array");
1020
+ };
1021
+ if (summary.mustBlob) {
1022
+ if (summary.counts.has(0))
1023
+ carriers.add("string");
1024
+ if (summary.counts.has(1) || summary.counts.has(2))
1025
+ carriers.add("array");
1026
+ }
1027
+ else {
1028
+ addNoBlobCarriers();
1029
+ if (summary.mayBlob) {
1030
+ if (summary.counts.has(0))
1031
+ carriers.add("string");
1032
+ if (summary.counts.has(1) || summary.counts.has(2))
1033
+ carriers.add("array");
1034
+ }
1035
+ }
1036
+ return ["string", "array", "object"].filter((kind) => carriers.has(kind));
1037
+ }
740
1038
  function collectDragDropPayloadHelpers(spec, cppTypes, cppCodecCatalog) {
741
1039
  const seen = new Set();
742
1040
  const helpers = [];
@@ -748,21 +1046,96 @@ function collectDragDropPayloadHelpers(spec, cppTypes, cppCodecCatalog) {
748
1046
  if (seen.has(typeName))
749
1047
  continue;
750
1048
  seen.add(typeName);
751
- const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(cppCodecCatalog, service.name, member.name);
1049
+ const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(cppCodecCatalog, service.name, member.name);
752
1050
  if (!payloadSite)
753
1051
  continue;
1052
+ const plan = cppCodecCatalog.plansByCodecId.get(payloadSite.codecId);
1053
+ if (!plan)
1054
+ continue;
754
1055
  helpers.push({
755
1056
  typeName,
756
1057
  cppType: cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]),
757
- codecId: payloadSite.codecId
1058
+ codecId: payloadSite.codecId,
1059
+ carriers: inferDragDropCarrierKinds(plan)
758
1060
  });
759
1061
  }
760
1062
  }
761
1063
  return helpers;
762
1064
  }
1065
+ function collectTsDragDropPayloadHelpers(spec, codecCatalog) {
1066
+ const seen = new Set();
1067
+ const helpers = [];
1068
+ for (const service of spec.services) {
1069
+ for (const member of service.members) {
1070
+ if ((member.kind !== "DropTarget" && member.kind !== "HoverTarget") || !member.payloadTypeText)
1071
+ continue;
1072
+ const typeName = member.payloadTypeText.replace(/\s/g, "");
1073
+ if (seen.has(typeName))
1074
+ continue;
1075
+ seen.add(typeName);
1076
+ const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(codecCatalog, service.name, member.name);
1077
+ if (!payloadSite)
1078
+ continue;
1079
+ const plan = codecCatalog.plansByCodecId.get(payloadSite.codecId);
1080
+ if (!plan)
1081
+ continue;
1082
+ helpers.push({
1083
+ typeName,
1084
+ tsType: mapTypeTextToTs(member.payloadTypeText),
1085
+ codecId: payloadSite.codecId,
1086
+ carriers: inferDragDropCarrierKinds(plan)
1087
+ });
1088
+ }
1089
+ }
1090
+ return helpers;
1091
+ }
1092
+ function renderTsDragDropPayloadHelpers(spec, codecCatalog) {
1093
+ const helpers = collectTsDragDropPayloadHelpers(spec, codecCatalog);
1094
+ return helpers
1095
+ .map((helper) => {
1096
+ const lines = [
1097
+ `function decodeDragDropPayload_${helper.typeName}(rawPayload: unknown): ${helper.tsType} {`,
1098
+ ` if (typeof rawPayload !== "string") {`,
1099
+ ` throw new Error("Drag/drop payload must be tagged text.");`,
1100
+ ` }`,
1101
+ ` if (rawPayload.length === 0) {`,
1102
+ ` throw new Error("Drag/drop payload is empty.");`,
1103
+ ` }`,
1104
+ ` const transportTag = rawPayload[0];`,
1105
+ ` const payloadText = rawPayload.slice(1);`
1106
+ ];
1107
+ if (helper.carriers.includes("string")) {
1108
+ lines.push(` if (transportTag === "S") {`);
1109
+ lines.push(` return decode${helper.codecId}(payloadText);`);
1110
+ lines.push(` }`);
1111
+ }
1112
+ if (helper.carriers.includes("array")) {
1113
+ lines.push(` if (transportTag === "A") {`);
1114
+ lines.push(` const parsed = JSON.parse(payloadText) as unknown;`);
1115
+ lines.push(` if (!Array.isArray(parsed)) {`);
1116
+ lines.push(` throw new Error("Drag/drop payload must be a JSON array.");`);
1117
+ lines.push(` }`);
1118
+ lines.push(` return decode${helper.codecId}(parsed);`);
1119
+ lines.push(` }`);
1120
+ }
1121
+ if (helper.carriers.includes("object")) {
1122
+ lines.push(` if (transportTag === "O") {`);
1123
+ lines.push(` const parsed = JSON.parse(payloadText) as unknown;`);
1124
+ lines.push(` if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {`);
1125
+ lines.push(` throw new Error("Drag/drop payload must be a JSON object.");`);
1126
+ lines.push(` }`);
1127
+ lines.push(` return decode${helper.codecId}(parsed);`);
1128
+ lines.push(` }`);
1129
+ }
1130
+ lines.push(` throw new Error(\`Drag/drop payload has an unknown transport tag: \${transportTag}\`);`);
1131
+ lines.push(`}`);
1132
+ return lines.join("\n");
1133
+ })
1134
+ .join("\n\n");
1135
+ }
763
1136
  function renderTypesHeader(spec, cppTypes) {
764
1137
  const decls = cppTypes.orderedDecls.map(renderCppDecl).join("\n\n");
765
- const metatypes = cppTypes.structNames
1138
+ const metatypes = cppTypes.metatypeNames
766
1139
  .flatMap((name) => [
767
1140
  `Q_DECLARE_METATYPE(${spec.widgetName}::${name})`,
768
1141
  `Q_DECLARE_METATYPE(QList<${spec.widgetName}::${name}>)`
@@ -794,14 +1167,12 @@ ${metatypes}
794
1167
  }
795
1168
  function renderWidgetUmbrellaHeader(spec) {
796
1169
  return `#pragma once
797
- // Built by <AnQst_version>
798
1170
  #include "${spec.widgetName}Widget.h"
799
1171
  #include "${spec.widgetName}Types.h"
800
1172
  `;
801
1173
  }
802
- function renderWidgetHeader(spec, cppTypes) {
1174
+ function renderWidgetHeader(spec, cppTypes, cppCodecCatalog) {
803
1175
  const widgetClassName = `${spec.widgetName}Widget`;
804
- const cppCodecCatalog = (0, structured_top_level_codecs_1.buildStructuredCodecCatalog)(spec);
805
1176
  const dragDropPayloadHelpers = collectDragDropPayloadHelpers(spec, cppTypes, cppCodecCatalog);
806
1177
  const callbackAliases = [];
807
1178
  const publicMethods = [];
@@ -816,10 +1187,8 @@ function renderWidgetHeader(spec, cppTypes) {
816
1187
  `static QByteArray encodeDragDropPayload_${helper.typeName}(const ${helper.cppType}& payload);`,
817
1188
  `static std::optional<${helper.cppType}> decodeDragDropPayload_${helper.typeName}(const QByteArray& rawPayload);`
818
1189
  ]);
819
- const bindings = [];
820
1190
  for (const service of spec.services) {
821
1191
  for (const member of service.members) {
822
- bindings.push({ service: service.name, member: member.name, kind: member.kind });
823
1192
  const memberPascal = pascalCase(member.name);
824
1193
  if (member.kind === "Call" && member.payloadTypeText) {
825
1194
  const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
@@ -929,13 +1298,6 @@ private:
929
1298
  QVariantList args;
930
1299
  QDateTime enqueuedAt;
931
1300
  };
932
- struct BridgeBindingRow {
933
- const char* service;
934
- const char* member;
935
- const char* kind;
936
- };
937
- static const BridgeBindingRow kBridgeBindings[];
938
- static constexpr int kBridgeBindingsCount = ${bindings.length};
939
1301
  static QString makeBindingKey(const QString& service, const QString& member);
940
1302
  void installBridgeBindings();
941
1303
  bool hasEmitterListeners(const QString& service, const QString& member) const;
@@ -957,18 +1319,19 @@ ${fields.map((f) => ` ${f}`).join("\n")}
957
1319
  };
958
1320
  `;
959
1321
  }
960
- function renderCppStub(spec, cppTypes) {
1322
+ function renderCppStub(spec, cppTypes, cppCodecCatalog) {
961
1323
  const widgetClassName = `${spec.widgetName}Widget`;
962
- const cppCodecCatalog = (0, structured_top_level_codecs_1.buildStructuredCodecCatalog)(spec);
963
1324
  const dragDropPayloadHelpers = collectDragDropPayloadHelpers(spec, cppTypes, cppCodecCatalog);
964
- const cppCodecHelpers = (0, structured_top_level_codecs_1.renderCppStructuredCodecHelpers)(cppCodecCatalog, (typeText, pathHintParts) => cppTypes.mapTypeText(typeText, pathHintParts)).trim();
1325
+ const cppCodecHelpers = (0, boundary_codecs_1.renderCppBoundaryCodecHelpers)(cppCodecCatalog, (typeText, pathHintParts) => cppTypes.mapTypeText(typeText, pathHintParts)).trim();
965
1326
  const lines = [];
966
1327
  lines.push(`#include "include/${spec.widgetName}Widget.h"`);
1328
+ lines.push(`#include "AnQstBase93.h"`);
967
1329
  lines.push(`#include <QDebug>`);
968
1330
  lines.push(`#include <QElapsedTimer>`);
969
1331
  lines.push(`#include <QEventLoop>`);
970
1332
  lines.push(`#include <QJsonArray>`);
971
1333
  lines.push(`#include <QJsonDocument>`);
1334
+ lines.push(`#include <QJsonObject>`);
972
1335
  lines.push(`#include <QMetaType>`);
973
1336
  lines.push(`#include <QTimer>`);
974
1337
  lines.push(`#include <cstring>`);
@@ -984,7 +1347,7 @@ function renderCppStub(spec, cppTypes) {
984
1347
  lines.push("namespace {");
985
1348
  lines.push("void registerGeneratedMetaTypes() {");
986
1349
  lines.push(" static const bool registered = []() {");
987
- for (const typeName of cppTypes.structNames) {
1350
+ for (const typeName of cppTypes.metatypeNames) {
988
1351
  lines.push(` qRegisterMetaType<${spec.widgetName}::${typeName}>("${spec.widgetName}::${typeName}");`);
989
1352
  lines.push(` qRegisterMetaType<QList<${spec.widgetName}::${typeName}>>("QList<${spec.widgetName}::${typeName}>");`);
990
1353
  }
@@ -1000,20 +1363,78 @@ function renderCppStub(spec, cppTypes) {
1000
1363
  lines.push("");
1001
1364
  for (const helper of dragDropPayloadHelpers) {
1002
1365
  lines.push(`QByteArray ${widgetClassName}::encodeDragDropPayload_${helper.typeName}(const ${helper.cppType}& payload) {`);
1003
- lines.push(` return QJsonDocument::fromVariant(anqstNormalizeWireItems(encode${helper.codecId}(payload))).toJson(QJsonDocument::Compact);`);
1366
+ lines.push(` const QVariant wire = encode${helper.codecId}(payload);`);
1367
+ if (helper.carriers.includes("string")) {
1368
+ lines.push(` if (wire.type() == QVariant::String) {`);
1369
+ lines.push(` QByteArray out;`);
1370
+ lines.push(` out.append('S');`);
1371
+ lines.push(` out.append(wire.toString().toUtf8());`);
1372
+ lines.push(` return out;`);
1373
+ lines.push(` }`);
1374
+ }
1375
+ if (helper.carriers.includes("array")) {
1376
+ lines.push(` if (wire.type() == QVariant::List) {`);
1377
+ lines.push(` QByteArray out;`);
1378
+ lines.push(` out.append('A');`);
1379
+ lines.push(` out.append(QJsonDocument(QJsonArray::fromVariantList(wire.toList())).toJson(QJsonDocument::Compact));`);
1380
+ lines.push(` return out;`);
1381
+ lines.push(` }`);
1382
+ }
1383
+ if (helper.carriers.includes("object")) {
1384
+ lines.push(` if (wire.type() == QVariant::Map) {`);
1385
+ lines.push(` QByteArray out;`);
1386
+ lines.push(` out.append('O');`);
1387
+ lines.push(` out.append(QJsonDocument(QJsonObject::fromVariantMap(wire.toMap())).toJson(QJsonDocument::Compact));`);
1388
+ lines.push(` return out;`);
1389
+ lines.push(` }`);
1390
+ }
1391
+ lines.push(` throw std::runtime_error("AnQst drag/drop payload codec emitted an unsupported top-level carrier.");`);
1004
1392
  lines.push(`}`);
1005
1393
  lines.push("");
1006
1394
  lines.push(`std::optional<${helper.cppType}> ${widgetClassName}::decodeDragDropPayload_${helper.typeName}(const QByteArray& rawPayload) {`);
1007
- lines.push(` QJsonParseError parseError;`);
1008
- lines.push(` const QJsonDocument document = QJsonDocument::fromJson(rawPayload, &parseError);`);
1009
- lines.push(` if (parseError.error != QJsonParseError::NoError || !document.isArray()) {`);
1010
- lines.push(` return std::nullopt;`);
1011
- lines.push(` }`);
1012
- lines.push(` try {`);
1013
- lines.push(` return decode${helper.codecId}(document.array().toVariantList());`);
1014
- lines.push(` } catch (...) {`);
1395
+ lines.push(` if (rawPayload.isEmpty()) {`);
1015
1396
  lines.push(` return std::nullopt;`);
1016
1397
  lines.push(` }`);
1398
+ lines.push(` const char transportTag = rawPayload.at(0);`);
1399
+ lines.push(` const QByteArray payloadBytes = rawPayload.mid(1);`);
1400
+ if (helper.carriers.includes("string")) {
1401
+ lines.push(` if (transportTag == 'S') {`);
1402
+ lines.push(` try {`);
1403
+ lines.push(` return decode${helper.codecId}(QString::fromUtf8(payloadBytes));`);
1404
+ lines.push(` } catch (...) {`);
1405
+ lines.push(` return std::nullopt;`);
1406
+ lines.push(` }`);
1407
+ lines.push(` }`);
1408
+ }
1409
+ if (helper.carriers.includes("array")) {
1410
+ lines.push(` if (transportTag == 'A') {`);
1411
+ lines.push(` QJsonParseError parseError;`);
1412
+ lines.push(` const QJsonDocument document = QJsonDocument::fromJson(payloadBytes, &parseError);`);
1413
+ lines.push(` if (parseError.error != QJsonParseError::NoError || !document.isArray()) {`);
1414
+ lines.push(` return std::nullopt;`);
1415
+ lines.push(` }`);
1416
+ lines.push(` try {`);
1417
+ lines.push(` return decode${helper.codecId}(QVariant(document.array().toVariantList()));`);
1418
+ lines.push(` } catch (...) {`);
1419
+ lines.push(` return std::nullopt;`);
1420
+ lines.push(` }`);
1421
+ lines.push(` }`);
1422
+ }
1423
+ if (helper.carriers.includes("object")) {
1424
+ lines.push(` if (transportTag == 'O') {`);
1425
+ lines.push(` QJsonParseError parseError;`);
1426
+ lines.push(` const QJsonDocument document = QJsonDocument::fromJson(payloadBytes, &parseError);`);
1427
+ lines.push(` if (parseError.error != QJsonParseError::NoError || !document.isObject()) {`);
1428
+ lines.push(` return std::nullopt;`);
1429
+ lines.push(` }`);
1430
+ lines.push(` try {`);
1431
+ lines.push(` return decode${helper.codecId}(QVariant(document.object().toVariantMap()));`);
1432
+ lines.push(` } catch (...) {`);
1433
+ lines.push(` return std::nullopt;`);
1434
+ lines.push(` }`);
1435
+ lines.push(` }`);
1436
+ }
1437
+ lines.push(` return std::nullopt;`);
1017
1438
  lines.push(`}`);
1018
1439
  lines.push("");
1019
1440
  }
@@ -1033,14 +1454,6 @@ function renderCppStub(spec, cppTypes) {
1033
1454
  lines.push("");
1034
1455
  }
1035
1456
  }
1036
- lines.push(`const ${widgetClassName}::BridgeBindingRow ${widgetClassName}::kBridgeBindings[] = {`);
1037
- for (const service of spec.services) {
1038
- for (const member of service.members) {
1039
- lines.push(` {"${service.name}", "${member.name}", "${member.kind}"},`);
1040
- }
1041
- }
1042
- lines.push(`};`);
1043
- lines.push("");
1044
1457
  lines.push(`${widgetClassName}::${widgetClassName}(QWidget* parent) : AnQstWebHostBase(parent), handle(this) {`);
1045
1458
  lines.push(` static const bool kResourcesInitialized = []() {`);
1046
1459
  lines.push(` ::qInitResources_${spec.widgetName}();`);
@@ -1067,19 +1480,89 @@ function renderCppStub(spec, cppTypes) {
1067
1480
  for (const member of service.members) {
1068
1481
  if (member.kind === "DropTarget" && member.payloadTypeText) {
1069
1482
  const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
1070
- const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(cppCodecCatalog, service.name, member.name);
1483
+ const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(cppCodecCatalog, service.name, member.name);
1484
+ const typeName = member.payloadTypeText.replace(/\s/g, "");
1071
1485
  lines.push(` QObject::connect(this, &AnQstWebHostBase::anQstBridge_dropReceived, this, [this](const QString& service, const QString& member, const QVariant& payload, double x, double y) {`);
1072
1486
  lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
1073
- lines.push(` emit ${member.name}(${payloadSite ? `decode${payloadSite.codecId}(payload)` : `payload.value<${cppType}>()`}, x, y);`);
1487
+ if (payloadSite) {
1488
+ lines.push(` if (payload.type() != QVariant::String) {`);
1489
+ lines.push(` emitHostError(`);
1490
+ lines.push(` QStringLiteral("DeserializationError"),`);
1491
+ lines.push(` QStringLiteral("bridge"),`);
1492
+ lines.push(` QStringLiteral("error"),`);
1493
+ lines.push(` true,`);
1494
+ lines.push(` QStringLiteral("Failed to deserialize DropTarget ${service.name}.${member.name}."),`);
1495
+ lines.push(` {`);
1496
+ lines.push(` {QStringLiteral("service"), QStringLiteral("${service.name}")},`);
1497
+ lines.push(` {QStringLiteral("member"), QStringLiteral("${member.name}")},`);
1498
+ lines.push(` {QStringLiteral("detail"), QStringLiteral("Host did not provide tagged drag/drop payload text.")},`);
1499
+ lines.push(` });`);
1500
+ lines.push(` return;`);
1501
+ lines.push(` }`);
1502
+ lines.push(` const auto decodedPayload = decodeDragDropPayload_${typeName}(payload.toString().toUtf8());`);
1503
+ lines.push(` if (!decodedPayload.has_value()) {`);
1504
+ lines.push(` emitHostError(`);
1505
+ lines.push(` QStringLiteral("DeserializationError"),`);
1506
+ lines.push(` QStringLiteral("bridge"),`);
1507
+ lines.push(` QStringLiteral("error"),`);
1508
+ lines.push(` true,`);
1509
+ lines.push(` QStringLiteral("Failed to deserialize DropTarget ${service.name}.${member.name}."),`);
1510
+ lines.push(` {`);
1511
+ lines.push(` {QStringLiteral("service"), QStringLiteral("${service.name}")},`);
1512
+ lines.push(` {QStringLiteral("member"), QStringLiteral("${member.name}")},`);
1513
+ lines.push(` {QStringLiteral("detail"), QStringLiteral("Tagged drag/drop payload did not match the planned boundary carrier.")},`);
1514
+ lines.push(` });`);
1515
+ lines.push(` return;`);
1516
+ lines.push(` }`);
1517
+ lines.push(` emit ${member.name}(*decodedPayload, x, y);`);
1518
+ }
1519
+ else {
1520
+ lines.push(` emit ${member.name}(payload.value<${cppType}>(), x, y);`);
1521
+ }
1074
1522
  lines.push(` }`);
1075
1523
  lines.push(` });`);
1076
1524
  }
1077
1525
  else if (member.kind === "HoverTarget" && member.payloadTypeText) {
1078
1526
  const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
1079
- const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(cppCodecCatalog, service.name, member.name);
1527
+ const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(cppCodecCatalog, service.name, member.name);
1528
+ const typeName = member.payloadTypeText.replace(/\s/g, "");
1080
1529
  lines.push(` QObject::connect(this, &AnQstWebHostBase::anQstBridge_hoverUpdated, this, [this](const QString& service, const QString& member, const QVariant& payload, double x, double y) {`);
1081
1530
  lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
1082
- lines.push(` emit ${member.name}(${payloadSite ? `decode${payloadSite.codecId}(payload)` : `payload.value<${cppType}>()`}, x, y);`);
1531
+ if (payloadSite) {
1532
+ lines.push(` if (payload.type() != QVariant::String) {`);
1533
+ lines.push(` emitHostError(`);
1534
+ lines.push(` QStringLiteral("DeserializationError"),`);
1535
+ lines.push(` QStringLiteral("bridge"),`);
1536
+ lines.push(` QStringLiteral("error"),`);
1537
+ lines.push(` true,`);
1538
+ lines.push(` QStringLiteral("Failed to deserialize HoverTarget ${service.name}.${member.name}."),`);
1539
+ lines.push(` {`);
1540
+ lines.push(` {QStringLiteral("service"), QStringLiteral("${service.name}")},`);
1541
+ lines.push(` {QStringLiteral("member"), QStringLiteral("${member.name}")},`);
1542
+ lines.push(` {QStringLiteral("detail"), QStringLiteral("Host did not provide tagged drag/drop payload text.")},`);
1543
+ lines.push(` });`);
1544
+ lines.push(` return;`);
1545
+ lines.push(` }`);
1546
+ lines.push(` const auto decodedPayload = decodeDragDropPayload_${typeName}(payload.toString().toUtf8());`);
1547
+ lines.push(` if (!decodedPayload.has_value()) {`);
1548
+ lines.push(` emitHostError(`);
1549
+ lines.push(` QStringLiteral("DeserializationError"),`);
1550
+ lines.push(` QStringLiteral("bridge"),`);
1551
+ lines.push(` QStringLiteral("error"),`);
1552
+ lines.push(` true,`);
1553
+ lines.push(` QStringLiteral("Failed to deserialize HoverTarget ${service.name}.${member.name}."),`);
1554
+ lines.push(` {`);
1555
+ lines.push(` {QStringLiteral("service"), QStringLiteral("${service.name}")},`);
1556
+ lines.push(` {QStringLiteral("member"), QStringLiteral("${member.name}")},`);
1557
+ lines.push(` {QStringLiteral("detail"), QStringLiteral("Tagged drag/drop payload did not match the planned boundary carrier.")},`);
1558
+ lines.push(` });`);
1559
+ lines.push(` return;`);
1560
+ lines.push(` }`);
1561
+ lines.push(` emit ${member.name}(*decodedPayload, x, y);`);
1562
+ }
1563
+ else {
1564
+ lines.push(` emit ${member.name}(payload.value<${cppType}>(), x, y);`);
1565
+ }
1083
1566
  lines.push(` }`);
1084
1567
  lines.push(` });`);
1085
1568
  lines.push(` QObject::connect(this, &AnQstWebHostBase::anQstBridge_hoverLeft, this, [this](const QString& service, const QString& member) {`);
@@ -1185,12 +1668,12 @@ function renderCppStub(spec, cppTypes) {
1185
1668
  continue;
1186
1669
  const timeoutMs = member.timeoutMs;
1187
1670
  const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
1188
- const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(cppCodecCatalog, service.name, member.name);
1671
+ const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(cppCodecCatalog, service.name, member.name);
1189
1672
  lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
1190
1673
  for (let i = 0; i < member.parameters.length; i++) {
1191
1674
  const p = member.parameters[i];
1192
1675
  const pType = cppTypes.mapTypeText(p.typeText, [service.name, member.name, p.name]);
1193
- const paramSite = (0, structured_top_level_codecs_1.getStructuredParameterSite)(cppCodecCatalog, service.name, member.name, p.name);
1676
+ const paramSite = (0, boundary_codecs_1.getBoundaryParameterSite)(cppCodecCatalog, service.name, member.name, p.name);
1194
1677
  lines.push(` const ${pType} ${p.name} = ${paramSite ? `decode${paramSite.codecId}(args.value(${i}))` : variantToCppExpression(pType, `args.value(${i})`)};`);
1195
1678
  }
1196
1679
  lines.push(` const QString requestId = QStringLiteral("call-%1").arg(++m_callRequestCounter);`);
@@ -1260,7 +1743,7 @@ function renderCppStub(spec, cppTypes) {
1260
1743
  for (let i = 0; i < member.parameters.length; i++) {
1261
1744
  const p = member.parameters[i];
1262
1745
  const pType = cppTypes.mapTypeText(p.typeText, [service.name, member.name, p.name]);
1263
- const paramSite = (0, structured_top_level_codecs_1.getStructuredParameterSite)(cppCodecCatalog, service.name, member.name, p.name);
1746
+ const paramSite = (0, boundary_codecs_1.getBoundaryParameterSite)(cppCodecCatalog, service.name, member.name, p.name);
1264
1747
  lines.push(` const ${pType} ${p.name} = ${paramSite ? `decode${paramSite.codecId}(args.value(${i}))` : variantToCppExpression(pType, `args.value(${i})`)};`);
1265
1748
  }
1266
1749
  const argNames = member.parameters.map((p) => p.name).join(", ");
@@ -1277,7 +1760,7 @@ function renderCppStub(spec, cppTypes) {
1277
1760
  if (member.kind !== "Input" || !member.payloadTypeText)
1278
1761
  continue;
1279
1762
  const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
1280
- const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(cppCodecCatalog, service.name, member.name);
1763
+ const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(cppCodecCatalog, service.name, member.name);
1281
1764
  lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
1282
1765
  lines.push(` const ${cppType} typedValue = ${payloadSite ? `decode${payloadSite.codecId}(value)` : variantToCppExpression(cppType, "value")};`);
1283
1766
  lines.push(` set${pascalCase(member.name)}(typedValue);`);
@@ -1299,13 +1782,13 @@ function renderCppStub(spec, cppTypes) {
1299
1782
  }
1300
1783
  if (member.kind === "Slot") {
1301
1784
  const ret = member.payloadTypeText ? cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]) : "void";
1302
- const payloadSite = member.payloadTypeText ? (0, structured_top_level_codecs_1.getStructuredPayloadSite)(cppCodecCatalog, service.name, member.name) : undefined;
1785
+ const payloadSite = member.payloadTypeText ? (0, boundary_codecs_1.getBoundaryPayloadSite)(cppCodecCatalog, service.name, member.name) : undefined;
1303
1786
  const args = member.parameters.map((p) => `${cppTypes.mapTypeText(p.typeText, [service.name, member.name, p.name])} ${p.name}`).join(", ");
1304
1787
  lines.push(`${ret} ${widgetClassName}::slot_${member.name}(${args}) {`);
1305
1788
  lines.push(` QVariantList invokeArgs;`);
1306
1789
  for (const p of member.parameters) {
1307
1790
  const pType = mapTsTypeToCpp(p.typeText);
1308
- const paramSite = (0, structured_top_level_codecs_1.getStructuredParameterSite)(cppCodecCatalog, service.name, member.name, p.name);
1791
+ const paramSite = (0, boundary_codecs_1.getBoundaryParameterSite)(cppCodecCatalog, service.name, member.name, p.name);
1309
1792
  lines.push(` invokeArgs.push_back(${paramSite ? `encode${paramSite.codecId}(${p.name})` : cppToVariantExpression(pType, p.name)});`);
1310
1793
  }
1311
1794
  lines.push(` QVariant result;`);
@@ -1330,7 +1813,7 @@ function renderCppStub(spec, cppTypes) {
1330
1813
  }
1331
1814
  else if ((member.kind === "Input" || member.kind === "Output") && member.payloadTypeText) {
1332
1815
  const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
1333
- const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(cppCodecCatalog, service.name, member.name);
1816
+ const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(cppCodecCatalog, service.name, member.name);
1334
1817
  const cap = member.name.charAt(0).toUpperCase() + member.name.slice(1);
1335
1818
  lines.push(`${cppType} ${widgetClassName}::${member.name}() const {`);
1336
1819
  lines.push(` return m_${member.name};`);
@@ -1567,11 +2050,11 @@ function renderTsService(spec, serviceName, codecCatalog) {
1567
2050
  const constructorBodyLines = [];
1568
2051
  for (const m of members) {
1569
2052
  const args = m.parameters.map((p) => `${p.name}: ${mapTypeTextToTs(p.typeText)}`).join(", ");
1570
- const paramSites = m.parameters.map((p) => (0, structured_top_level_codecs_1.getStructuredParameterSite)(codecCatalog, serviceName, m.name, p.name));
2053
+ const paramSites = m.parameters.map((p) => (0, boundary_codecs_1.getBoundaryParameterSite)(codecCatalog, serviceName, m.name, p.name));
1571
2054
  const encodedValueArray = paramSites.length > 0
1572
2055
  ? `[${m.parameters.map((p, index) => `${paramSites[index] ? `encode${paramSites[index].codecId}(${p.name})` : p.name}`).join(", ")}]`
1573
2056
  : "[]";
1574
- const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(codecCatalog, serviceName, m.name);
2057
+ const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(codecCatalog, serviceName, m.name);
1575
2058
  if (m.kind === "Call") {
1576
2059
  const ret = mapTypeTextToTs(m.payloadTypeText ?? "void");
1577
2060
  if (payloadSite) {
@@ -1649,30 +2132,18 @@ function renderTsService(spec, serviceName, codecCatalog) {
1649
2132
  }
1650
2133
  if (m.kind === "Output") {
1651
2134
  constructorBodyLines.push(` this._bridge.onOutput("${serviceName}", "${m.name}", (value) => {`);
1652
- constructorBodyLines.push(` try {`);
1653
- constructorBodyLines.push(` this._${m.name}.set(${payloadSite ? `decode${payloadSite.codecId}(value)` : `value as ${tsType}`});`);
1654
- constructorBodyLines.push(` } catch (error) {`);
1655
- constructorBodyLines.push(` this._bridge.reportFrontendDiagnostic({`);
1656
- constructorBodyLines.push(` code: "DeserializationError",`);
1657
- constructorBodyLines.push(` severity: "error",`);
1658
- constructorBodyLines.push(` category: "bridge",`);
1659
- constructorBodyLines.push(` recoverable: true,`);
1660
- constructorBodyLines.push(` message: \`Failed to deserialize Output ${serviceName}.${m.name}: \${errorMessage(error)}\`,`);
1661
- constructorBodyLines.push(` service: "${serviceName}",`);
1662
- constructorBodyLines.push(` member: "${m.name}",`);
1663
- constructorBodyLines.push(` context: { interaction: "Output" }`);
1664
- constructorBodyLines.push(` });`);
1665
- constructorBodyLines.push(` }`);
2135
+ constructorBodyLines.push(` this._${m.name}.set(${payloadSite ? `decode${payloadSite.codecId}(value)` : `value as ${tsType}`});`);
1666
2136
  constructorBodyLines.push(` });`);
1667
2137
  }
1668
2138
  }
1669
2139
  if (m.kind === "DropTarget" && m.payloadTypeText) {
1670
2140
  const tsType = mapTypeTextToTs(m.payloadTypeText);
2141
+ const typeName = m.payloadTypeText.replace(/\s/g, "");
1671
2142
  fieldLines.push(` private readonly _${m.name} = signal<{ payload: ${tsType}; x: number; y: number } | null>(null);`);
1672
2143
  methodLines.push(` ${m.name}(): { payload: ${tsType}; x: number; y: number } | null { return this._${m.name}(); }`);
1673
2144
  constructorBodyLines.push(` this._bridge.onDrop("${serviceName}", "${m.name}", (payload, x, y) => {`);
1674
2145
  constructorBodyLines.push(` try {`);
1675
- constructorBodyLines.push(` this._${m.name}.set({ payload: ${payloadSite ? `decode${payloadSite.codecId}(payload)` : `payload as ${tsType}`}, x, y });`);
2146
+ constructorBodyLines.push(` this._${m.name}.set({ payload: ${payloadSite ? `decodeDragDropPayload_${typeName}(payload)` : `payload as ${tsType}`}, x, y });`);
1676
2147
  constructorBodyLines.push(` } catch (error) {`);
1677
2148
  constructorBodyLines.push(` this._bridge.reportFrontendDiagnostic({`);
1678
2149
  constructorBodyLines.push(` code: "DeserializationError",`);
@@ -1689,11 +2160,12 @@ function renderTsService(spec, serviceName, codecCatalog) {
1689
2160
  }
1690
2161
  if (m.kind === "HoverTarget" && m.payloadTypeText) {
1691
2162
  const tsType = mapTypeTextToTs(m.payloadTypeText);
2163
+ const typeName = m.payloadTypeText.replace(/\s/g, "");
1692
2164
  fieldLines.push(` private readonly _${m.name} = signal<{ payload: ${tsType}; x: number; y: number } | null>(null);`);
1693
2165
  methodLines.push(` ${m.name}(): { payload: ${tsType}; x: number; y: number } | null { return this._${m.name}(); }`);
1694
2166
  constructorBodyLines.push(` this._bridge.onHover("${serviceName}", "${m.name}", (payload, x, y) => {`);
1695
2167
  constructorBodyLines.push(` try {`);
1696
- constructorBodyLines.push(` this._${m.name}.set({ payload: ${payloadSite ? `decode${payloadSite.codecId}(payload)` : `payload as ${tsType}`}, x, y });`);
2168
+ constructorBodyLines.push(` this._${m.name}.set({ payload: ${payloadSite ? `decodeDragDropPayload_${typeName}(payload)` : `payload as ${tsType}`}, x, y });`);
1697
2169
  constructorBodyLines.push(` } catch (error) {`);
1698
2170
  constructorBodyLines.push(` this._bridge.reportFrontendDiagnostic({`);
1699
2171
  constructorBodyLines.push(` code: "DeserializationError",`);
@@ -1785,18 +2257,20 @@ export declare class ${serviceName} {
1785
2257
  readonly onSlot: ${onSlotInterfaceName};${classMemberBlock}
1786
2258
  }`;
1787
2259
  }
1788
- function renderTsServices(spec) {
1789
- const codecCatalog = (0, structured_top_level_codecs_1.buildStructuredCodecCatalog)(spec);
2260
+ function renderTsServices(spec, codecCatalog) {
1790
2261
  const serviceClasses = spec.services.map((s) => renderTsService(spec, s.name, codecCatalog)).join("\n");
1791
2262
  const externalTypeImports = renderRequiredTypeImports(spec, `frontend/${(0, layout_1.generatedFrontendDirName)(spec.widgetName)}/services.ts`).trim();
1792
2263
  const localTypeImports = renderLocalTypeImports(spec).trim();
1793
2264
  const typeImports = [externalTypeImports, localTypeImports].filter((s) => s.length > 0).join("\n");
1794
2265
  const typeImportsBlock = typeImports.length > 0 ? `${typeImports}\n\n` : "";
2266
+ const dragDropHelperBlock = renderTsDragDropPayloadHelpers(spec, codecCatalog).trim();
2267
+ const dragDropHelpers = dragDropHelperBlock.length > 0 ? `\n// Drag/drop payload helpers\n${dragDropHelperBlock}\n` : "";
1795
2268
  return `import { Injectable, inject, signal } from "@angular/core";
1796
2269
  ${typeImportsBlock}
1797
2270
 
1798
- // Structured/top-level codec helpers
1799
- ${(0, structured_top_level_codecs_1.renderTsStructuredCodecHelpers)(codecCatalog)}
2271
+ // Boundary codec plan helpers
2272
+ ${(0, boundary_codecs_1.renderTsBoundaryCodecHelpers)(codecCatalog)}
2273
+ ${dragDropHelpers}
1800
2274
 
1801
2275
  type SlotHandler = (...args: unknown[]) => unknown;
1802
2276
  type OutputHandler = (value: unknown) => void;
@@ -2717,8 +3191,7 @@ function renderNodeExpressWsTypes(spec) {
2717
3191
  const sections = [typeImports, typeDecls].filter((s) => s.length > 0);
2718
3192
  return sections.length > 0 ? `${sections.join("\n\n")}\n` : "";
2719
3193
  }
2720
- function renderNodeExpressWsIndex(spec) {
2721
- const codecCatalog = (0, structured_top_level_codecs_1.buildStructuredCodecCatalog)(spec);
3194
+ function renderNodeExpressWsIndex(spec, codecCatalog) {
2722
3195
  const typeImports = renderRequiredTypeImports(spec, `backend/node/express/${generatedNodeExpressWsDirName(spec.widgetName)}/index.ts`);
2723
3196
  const typeDecls = renderTypeDeclarations(spec, true);
2724
3197
  const handlerBridgeTypeName = `${spec.widgetName}HandlerBridge`;
@@ -2750,8 +3223,8 @@ function renderNodeExpressWsIndex(spec) {
2750
3223
  .map((member) => {
2751
3224
  const ret = mapTypeTextToTs(member.payloadTypeText ?? "void");
2752
3225
  const args = nodeParamArgs(member);
2753
- const paramSites = member.parameters.map((p) => (0, structured_top_level_codecs_1.getStructuredParameterSite)(codecCatalog, service.name, member.name, p.name));
2754
- const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(codecCatalog, service.name, member.name);
3226
+ const paramSites = member.parameters.map((p) => (0, boundary_codecs_1.getBoundaryParameterSite)(codecCatalog, service.name, member.name, p.name));
3227
+ const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(codecCatalog, service.name, member.name);
2755
3228
  const encodedArgs = member.parameters.length > 0
2756
3229
  ? `[${member.parameters.map((p, index) => `${paramSites[index] ? `encode${paramSites[index].codecId}(${p.name})` : p.name}`).join(", ")}]`
2757
3230
  : "[]";
@@ -2765,7 +3238,7 @@ function renderNodeExpressWsIndex(spec) {
2765
3238
  .filter((member) => member.kind === "Output" && member.payloadTypeText)
2766
3239
  .map((member) => {
2767
3240
  const typeText = mapTypeTextToTs(member.payloadTypeText);
2768
- const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(codecCatalog, service.name, member.name);
3241
+ const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(codecCatalog, service.name, member.name);
2769
3242
  return ` set${service.name}_${nodeCap(member.name)}(value: ${typeText}): void {
2770
3243
  this.setOutputValue("${service.name}", "${member.name}", ${payloadSite ? `encode${payloadSite.codecId}(value)` : "value"});
2771
3244
  }`;
@@ -2820,7 +3293,7 @@ function renderNodeExpressWsIndex(spec) {
2820
3293
  .filter((member) => (member.kind === "Input" || member.kind === "Output") && member.payloadTypeText)
2821
3294
  .map((member) => {
2822
3295
  const typeText = mapTypeTextToTs(member.payloadTypeText);
2823
- const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(codecCatalog, service.name, member.name);
3296
+ const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(codecCatalog, service.name, member.name);
2824
3297
  if (member.kind === "Input") {
2825
3298
  return ` ${member.name}: {\n get: () => session.readInput("${service.name}", "${member.name}").then((value) => ${payloadSite ? `decode${payloadSite.codecId}(value)` : `value as ${typeText}`}),\n on: (handler: (value: ${typeText}) => void) => session.onInput("${service.name}", "${member.name}", (value) => handler(${payloadSite ? `decode${payloadSite.codecId}(value)` : `value as ${typeText}`}))\n },`;
2826
3299
  }
@@ -2834,8 +3307,8 @@ function renderNodeExpressWsIndex(spec) {
2834
3307
  .flatMap((service) => service.members
2835
3308
  .filter((member) => member.kind === "Call" && member.payloadTypeText)
2836
3309
  .map((member) => {
2837
- const paramSites = member.parameters.map((p) => (0, structured_top_level_codecs_1.getStructuredParameterSite)(codecCatalog, service.name, member.name, p.name));
2838
- const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(codecCatalog, service.name, member.name);
3310
+ const paramSites = member.parameters.map((p) => (0, boundary_codecs_1.getBoundaryParameterSite)(codecCatalog, service.name, member.name, p.name));
3311
+ const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(codecCatalog, service.name, member.name);
2839
3312
  const decodedArgs = member.parameters.length > 0
2840
3313
  ? member.parameters.map((p, index) => `${paramSites[index] ? `decode${paramSites[index].codecId}(args[${index}])` : `args[${index}] as ${mapTypeTextToTs(p.typeText)}`}`).join(", ")
2841
3314
  : "";
@@ -2890,7 +3363,7 @@ function renderNodeExpressWsIndex(spec) {
2890
3363
  .flatMap((service) => service.members
2891
3364
  .filter((member) => member.kind === "Emitter")
2892
3365
  .map((member) => {
2893
- const paramSites = member.parameters.map((p) => (0, structured_top_level_codecs_1.getStructuredParameterSite)(codecCatalog, service.name, member.name, p.name));
3366
+ const paramSites = member.parameters.map((p) => (0, boundary_codecs_1.getBoundaryParameterSite)(codecCatalog, service.name, member.name, p.name));
2894
3367
  const decodedArgs = member.parameters.length > 0
2895
3368
  ? member.parameters.map((p, index) => `${paramSites[index] ? `decode${paramSites[index].codecId}(args[${index}])` : `args[${index}] as ${mapTypeTextToTs(p.typeText)}`}`).join(", ")
2896
3369
  : "";
@@ -2935,7 +3408,7 @@ function renderNodeExpressWsIndex(spec) {
2935
3408
  .flatMap((service) => service.members
2936
3409
  .filter((member) => member.kind === "Input" && member.payloadTypeText)
2937
3410
  .map((member) => {
2938
- const payloadSite = (0, structured_top_level_codecs_1.getStructuredPayloadSite)(codecCatalog, service.name, member.name);
3411
+ const payloadSite = (0, boundary_codecs_1.getBoundaryPayloadSite)(codecCatalog, service.name, member.name);
2939
3412
  return ` if (service === "${service.name}" && member === "${member.name}") {
2940
3413
  const handler = implementation.${service.name}.${member.name};
2941
3414
  if (typeof handler !== "function") {
@@ -2976,8 +3449,8 @@ import type { WebSocket, WebSocketServer } from "ws";
2976
3449
  ${typeImports}
2977
3450
  ${typeDecls}
2978
3451
 
2979
- // Structured/top-level codec helpers
2980
- ${(0, structured_top_level_codecs_1.renderTsStructuredCodecHelpers)(codecCatalog)}
3452
+ // Boundary codec plan helpers
3453
+ ${(0, boundary_codecs_1.renderTsBoundaryCodecHelpers)(codecCatalog)}
2981
3454
 
2982
3455
  ${handlerInterfaces}
2983
3456
 
@@ -3439,7 +3912,23 @@ function renderTypeRootIndexDts(spec) {
3439
3912
  const typeDecls = renderTypeTypesDts(spec).trim();
3440
3913
  const serviceDecls = renderTypeServicesDts(spec).trim();
3441
3914
  const sections = [indexDecls, typeDecls, serviceDecls].filter((s) => s.length > 0);
3442
- return sections.length > 0 ? `${sections.join("\n\n")}\n` : "";
3915
+ if (sections.length === 0)
3916
+ return "";
3917
+ const dedupedLines = [];
3918
+ const seenImportLines = new Set();
3919
+ for (const line of sections.join("\n\n").split("\n")) {
3920
+ const trimmed = line.trim();
3921
+ if (!trimmed.startsWith("import type ")) {
3922
+ dedupedLines.push(line);
3923
+ continue;
3924
+ }
3925
+ if (seenImportLines.has(trimmed))
3926
+ continue;
3927
+ seenImportLines.add(trimmed);
3928
+ dedupedLines.push(line);
3929
+ }
3930
+ const deduped = dedupedLines.join("\n").replace(/\n{3,}/g, "\n\n");
3931
+ return `${deduped}\n`;
3443
3932
  }
3444
3933
  function generatedCppLibraryDirName(widgetName) {
3445
3934
  return (0, layout_1.generatedQtWidgetDirName)(widgetName);
@@ -3452,10 +3941,11 @@ function generateOutputs(spec, options = { emitQWidget: true, emitAngularService
3452
3941
  const cppDir = `backend/cpp/qt/${generatedCppLibraryDirName(spec.widgetName)}`;
3453
3942
  const nodeDir = `backend/node/express/${generatedNodeExpressWsDirName(spec.widgetName)}`;
3454
3943
  const outputs = {};
3944
+ const codecCatalog = (0, boundary_codecs_1.buildBoundaryCodecCatalog)(spec);
3455
3945
  if (options.emitAngularService) {
3456
3946
  outputs[`${frontendDir}/package.json`] = renderNpmPackage(spec);
3457
3947
  outputs[`${frontendDir}/index.ts`] = renderTsIndex();
3458
- outputs[`${frontendDir}/services.ts`] = renderTsServices(spec);
3948
+ outputs[`${frontendDir}/services.ts`] = renderTsServices(spec, codecCatalog);
3459
3949
  outputs[`${frontendDir}/types.ts`] = renderTsTypes(spec);
3460
3950
  outputs[`${frontendDir}/index.js`] = renderJsIndex();
3461
3951
  outputs[`${frontendDir}/services.js`] = renderJsServices();
@@ -3469,13 +3959,13 @@ function generateOutputs(spec, options = { emitQWidget: true, emitAngularService
3469
3959
  outputs[`${cppDir}/CMakeLists.txt`] = renderCMake(spec);
3470
3960
  outputs[`${cppDir}/${spec.widgetName}.qrc`] = renderEmbeddedQrc(spec.widgetName, []);
3471
3961
  outputs[`${cppDir}/include/${spec.widgetName}.h`] = renderWidgetUmbrellaHeader(spec);
3472
- outputs[`${cppDir}/include/${spec.widgetName}Widget.h`] = renderWidgetHeader(spec, cppTypes);
3962
+ outputs[`${cppDir}/include/${spec.widgetName}Widget.h`] = renderWidgetHeader(spec, cppTypes, codecCatalog);
3473
3963
  outputs[`${cppDir}/include/${spec.widgetName}Types.h`] = renderTypesHeader(spec, cppTypes);
3474
- outputs[`${cppDir}/${spec.widgetName}.cpp`] = renderCppStub(spec, cppTypes);
3964
+ outputs[`${cppDir}/${spec.widgetName}.cpp`] = renderCppStub(spec, cppTypes, codecCatalog);
3475
3965
  }
3476
3966
  if (options.emitNodeExpressWs) {
3477
3967
  outputs[`${nodeDir}/package.json`] = renderNodeExpressWsPackage(spec);
3478
- outputs[`${nodeDir}/index.ts`] = renderNodeExpressWsIndex(spec);
3968
+ outputs[`${nodeDir}/index.ts`] = renderNodeExpressWsIndex(spec, codecCatalog);
3479
3969
  outputs[`${nodeDir}/types/index.d.ts`] = renderNodeExpressWsTypes(spec);
3480
3970
  }
3481
3971
  return outputs;