@dusted/anqst 1.0.1 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/src/app.js +32 -9
- package/dist/src/base93.js +52 -0
- package/dist/src/boundary-codec-analysis.js +468 -0
- package/dist/src/boundary-codec-leaves.js +602 -0
- package/dist/src/boundary-codec-model.js +77 -0
- package/dist/src/boundary-codec-plan.js +522 -0
- package/dist/src/boundary-codec-render.js +1738 -0
- package/dist/src/boundary-codecs.js +174 -0
- package/dist/src/emit.js +1274 -150
- package/dist/src/program.js +1 -1
- package/package.json +2 -2
- package/spec/AnQst-Spec-DSL.d.ts +12 -6
package/README.md
CHANGED
|
@@ -75,6 +75,8 @@ Settings file (`./AnQst/<WidgetName>.settings.json`) owns project-local AnQst co
|
|
|
75
75
|
- If `QWidget` is enabled and `angular.json` exists:
|
|
76
76
|
- runs production Angular build
|
|
77
77
|
- embeds built web assets into generated Qt widget `webapp/`.
|
|
78
|
+
- Generated Qt integration CMake consumes the existing `./AnQst/generated` widget tree and fails fast if the required generated files are missing.
|
|
79
|
+
- Downstream CMake no longer invokes `npm`, `npx`, or `anqst`; run `anqst build` first, then build C++ against the generated tree.
|
|
78
80
|
- If `--designerplugin` is enabled:
|
|
79
81
|
- requires `ANQST_WEBBASE_DIR`
|
|
80
82
|
- emits plugin sources in `./AnQst/generated/backend/cpp/qt/<WidgetName>_widget/designerPlugin`
|
|
@@ -140,3 +142,16 @@ code AnQst/BurgerConstructor.AnQst.d.ts
|
|
|
140
142
|
npx @dusted/anqst test
|
|
141
143
|
npx @dusted/anqst build
|
|
142
144
|
```
|
|
145
|
+
|
|
146
|
+
## Two-stage workflow
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# Stage 1: Node/Angular/generation environment
|
|
150
|
+
npx @dusted/anqst build
|
|
151
|
+
|
|
152
|
+
# Stage 2: pure Qt/CMake environment, consuming the generated tree
|
|
153
|
+
cmake -S . -B build
|
|
154
|
+
cmake --build build
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Both stages must use the exact outputs from the same prior `anqst build` invocation.
|
package/dist/src/app.js
CHANGED
|
@@ -19,6 +19,12 @@ const project_1 = require("./project");
|
|
|
19
19
|
const layout_1 = require("./layout");
|
|
20
20
|
const parser_1 = require("./parser");
|
|
21
21
|
const verify_1 = require("./verify");
|
|
22
|
+
class CliUsageError extends Error {
|
|
23
|
+
constructor(message) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "CliUsageError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
22
28
|
const ANQSTGEN_ACTIVE_STAMP_FILE = ".anqstgen-version-active.json";
|
|
23
29
|
function renderHelp() {
|
|
24
30
|
const version = readActiveBuildStamp();
|
|
@@ -261,6 +267,7 @@ function runBuild(cwd, designerPlugin = false) {
|
|
|
261
267
|
detailLines.push(" Target QWidget:");
|
|
262
268
|
detailLines.push(` - Qt integration CMake: ${(0, layout_1.toProjectRelative)(cwd, node_path_1.default.join(layout.cppCmakeRoot, "CMakeLists.txt"))}`);
|
|
263
269
|
detailLines.push(` - Widget output root: ${(0, layout_1.toProjectRelative)(cwd, layout.cppQtWidgetRoot)}`);
|
|
270
|
+
detailLines.push(" - C++ handoff: downstream CMake consumes this generated tree directly");
|
|
264
271
|
detailLines.push(" - Embedded web assets refreshed from Angular build");
|
|
265
272
|
}
|
|
266
273
|
if (generationTargets.emitNodeExpressWs) {
|
|
@@ -295,12 +302,12 @@ function parseSpecCommandArg(commandName, specArg, extraArgs) {
|
|
|
295
302
|
const positional = [];
|
|
296
303
|
for (const arg of allArgs) {
|
|
297
304
|
if (arg.startsWith("-")) {
|
|
298
|
-
throw new
|
|
305
|
+
throw new CliUsageError(`Unknown ${commandName} flag '${arg}'. ${usageFor(commandName)}`);
|
|
299
306
|
}
|
|
300
307
|
positional.push(arg);
|
|
301
308
|
}
|
|
302
309
|
if (positional.length !== 1) {
|
|
303
|
-
throw new
|
|
310
|
+
throw new CliUsageError(usageFor(commandName));
|
|
304
311
|
}
|
|
305
312
|
return positional[0];
|
|
306
313
|
}
|
|
@@ -327,12 +334,12 @@ function parseBuildCommandArgs(specArg, extraArgs) {
|
|
|
327
334
|
continue;
|
|
328
335
|
}
|
|
329
336
|
if (arg.startsWith("-")) {
|
|
330
|
-
throw new
|
|
337
|
+
throw new CliUsageError(`Unknown build flag '${arg}'. ${usageFor("build")}`);
|
|
331
338
|
}
|
|
332
339
|
positional.push(arg);
|
|
333
340
|
}
|
|
334
341
|
if (positional.length > 0) {
|
|
335
|
-
throw new
|
|
342
|
+
throw new CliUsageError(`Unexpected extra argument '${positional[0]}'. ${usageFor("build")}`);
|
|
336
343
|
}
|
|
337
344
|
return { designerPlugin };
|
|
338
345
|
}
|
|
@@ -346,15 +353,15 @@ function parseCleanCommandArgs(specArg, extraArgs) {
|
|
|
346
353
|
continue;
|
|
347
354
|
}
|
|
348
355
|
if (arg.startsWith("-")) {
|
|
349
|
-
throw new
|
|
356
|
+
throw new CliUsageError(`Unknown clean flag '${arg}'. Use -f or --force.`);
|
|
350
357
|
}
|
|
351
358
|
if (targetPathArg !== null) {
|
|
352
|
-
throw new
|
|
359
|
+
throw new CliUsageError(`Unexpected extra argument '${arg}'. Usage: anqst clean <path> [-f|--force]`);
|
|
353
360
|
}
|
|
354
361
|
targetPathArg = arg;
|
|
355
362
|
}
|
|
356
363
|
if (targetPathArg === null) {
|
|
357
|
-
throw new
|
|
364
|
+
throw new CliUsageError("Usage: anqst clean <path> [-f|--force]");
|
|
358
365
|
}
|
|
359
366
|
return { targetPathArg, force };
|
|
360
367
|
}
|
|
@@ -475,6 +482,19 @@ function renderInstallAliasMessage() {
|
|
|
475
482
|
return text;
|
|
476
483
|
return `\x1b[38;5;214m${text}\x1b[0m`;
|
|
477
484
|
}
|
|
485
|
+
function formatUnexpectedError(error) {
|
|
486
|
+
if (!(error instanceof Error)) {
|
|
487
|
+
return `[AnQst] ${String(error)}`;
|
|
488
|
+
}
|
|
489
|
+
const lines = [`[AnQst] ${error.message}`];
|
|
490
|
+
const stack = typeof error.stack === "string" ? error.stack.trim() : "";
|
|
491
|
+
if (stack.length > 0) {
|
|
492
|
+
lines.push("");
|
|
493
|
+
lines.push("Stack trace:");
|
|
494
|
+
lines.push(stack);
|
|
495
|
+
}
|
|
496
|
+
return lines.join("\n");
|
|
497
|
+
}
|
|
478
498
|
function runCommand(command, specArg, extraArgs = []) {
|
|
479
499
|
try {
|
|
480
500
|
if (!command) {
|
|
@@ -544,8 +564,11 @@ function runCommand(command, specArg, extraArgs = []) {
|
|
|
544
564
|
console.error((0, errors_1.formatVerifyError)(error));
|
|
545
565
|
return 1;
|
|
546
566
|
}
|
|
547
|
-
|
|
548
|
-
|
|
567
|
+
if (error instanceof CliUsageError) {
|
|
568
|
+
console.error(error.message);
|
|
569
|
+
return 1;
|
|
570
|
+
}
|
|
571
|
+
console.error(formatUnexpectedError(error));
|
|
549
572
|
return 1;
|
|
550
573
|
}
|
|
551
574
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BASE93_ALPHABET = void 0;
|
|
4
|
+
exports.emitBase93Encoder = emitBase93Encoder;
|
|
5
|
+
exports.emitBase93Decoder = emitBase93Decoder;
|
|
6
|
+
exports.BASE93_ALPHABET = Array.from({ length: 95 }, (_, i) => String.fromCharCode(0x20 + i))
|
|
7
|
+
.filter(c => c !== '"' && c !== '\\')
|
|
8
|
+
.join('');
|
|
9
|
+
function emitBase93Encoder() {
|
|
10
|
+
return `function(d) {
|
|
11
|
+
var A = "${exports.BASE93_ALPHABET}", n = d.length, f = n >>> 2, r = n & 3,
|
|
12
|
+
o = new Array(f * 5 + (r ? r + 1 : 0)), p = 0, i, v, b, j;
|
|
13
|
+
for (i = 0; i < f; i++) {
|
|
14
|
+
b = i << 2;
|
|
15
|
+
v = ((d[b] << 24) | (d[b+1] << 16) | (d[b+2] << 8) | d[b+3]) >>> 0;
|
|
16
|
+
o[p+4] = A[v % 93]; v = (v / 93) | 0;
|
|
17
|
+
o[p+3] = A[v % 93]; v = (v / 93) | 0;
|
|
18
|
+
o[p+2] = A[v % 93]; v = (v / 93) | 0;
|
|
19
|
+
o[p+1] = A[v % 93];
|
|
20
|
+
o[p] = A[(v / 93) | 0];
|
|
21
|
+
p += 5;
|
|
22
|
+
}
|
|
23
|
+
if (r) {
|
|
24
|
+
b = f << 2; v = 0;
|
|
25
|
+
for (j = 0; j < r; j++) v = (v << 8) | d[b + j];
|
|
26
|
+
for (j = r; j >= 0; j--) { o[p + j] = A[v % 93]; v = (v / 93) | 0; }
|
|
27
|
+
}
|
|
28
|
+
return o.join("");
|
|
29
|
+
}`;
|
|
30
|
+
}
|
|
31
|
+
function emitBase93Decoder() {
|
|
32
|
+
return `function(s) {
|
|
33
|
+
var n = s.length, f = (n / 5) | 0, r = n - f * 5,
|
|
34
|
+
o = new Uint8Array(f * 4 + (r ? r - 1 : 0)), p = 0, i, v, c, b;
|
|
35
|
+
for (i = 0; i < f; i++) {
|
|
36
|
+
b = i * 5;
|
|
37
|
+
c = s.charCodeAt(b); v = c - 32 - ((c > 34) ? 1 : 0) - ((c > 92) ? 1 : 0);
|
|
38
|
+
c = s.charCodeAt(b + 1); v = v * 93 + c - 32 - ((c > 34) ? 1 : 0) - ((c > 92) ? 1 : 0);
|
|
39
|
+
c = s.charCodeAt(b + 2); v = v * 93 + c - 32 - ((c > 34) ? 1 : 0) - ((c > 92) ? 1 : 0);
|
|
40
|
+
c = s.charCodeAt(b + 3); v = v * 93 + c - 32 - ((c > 34) ? 1 : 0) - ((c > 92) ? 1 : 0);
|
|
41
|
+
c = s.charCodeAt(b + 4); v = v * 93 + c - 32 - ((c > 34) ? 1 : 0) - ((c > 92) ? 1 : 0);
|
|
42
|
+
o[p] = v >>> 24; o[p+1] = (v >>> 16) & 255; o[p+2] = (v >>> 8) & 255; o[p+3] = v & 255;
|
|
43
|
+
p += 4;
|
|
44
|
+
}
|
|
45
|
+
if (r) {
|
|
46
|
+
v = 0;
|
|
47
|
+
for (i = 0; i < r; i++) { c = s.charCodeAt(f * 5 + i); v = v * 93 + c - 32 - ((c > 34) ? 1 : 0) - ((c > 92) ? 1 : 0); }
|
|
48
|
+
for (i = r - 2; i >= 0; i--) { o[p + i] = v & 255; v = (v / 256) | 0; }
|
|
49
|
+
}
|
|
50
|
+
return o;
|
|
51
|
+
}`;
|
|
52
|
+
}
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BoundaryTransportAnalyzer = void 0;
|
|
7
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
+
const errors_1 = require("./errors");
|
|
9
|
+
const boundary_codec_model_1 = require("./boundary-codec-model");
|
|
10
|
+
const boundary_codec_leaves_1 = require("./boundary-codec-leaves");
|
|
11
|
+
const BUILTIN_TYPE_REFS = new Set(["Array", "ReadonlyArray", "Record", "Map", "Partial", "Promise"]);
|
|
12
|
+
function isStringLikeUnion(node) {
|
|
13
|
+
return node.types.every((part) => {
|
|
14
|
+
if (typescript_1.default.isLiteralTypeNode(part) && typescript_1.default.isStringLiteral(part.literal))
|
|
15
|
+
return true;
|
|
16
|
+
return part.kind === typescript_1.default.SyntaxKind.StringKeyword;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function isBooleanLikeUnion(node) {
|
|
20
|
+
return node.types.every((part) => {
|
|
21
|
+
if (typescript_1.default.isLiteralTypeNode(part)) {
|
|
22
|
+
return part.literal.kind === typescript_1.default.SyntaxKind.TrueKeyword || part.literal.kind === typescript_1.default.SyntaxKind.FalseKeyword;
|
|
23
|
+
}
|
|
24
|
+
return part.kind === typescript_1.default.SyntaxKind.BooleanKeyword;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function isNumberLikeUnion(node) {
|
|
28
|
+
return node.types.every((part) => {
|
|
29
|
+
if (typescript_1.default.isLiteralTypeNode(part) && typescript_1.default.isNumericLiteral(part.literal))
|
|
30
|
+
return true;
|
|
31
|
+
return part.kind === typescript_1.default.SyntaxKind.NumberKeyword || part.kind === typescript_1.default.SyntaxKind.BigIntKeyword;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function filterNullishUnionParts(types) {
|
|
35
|
+
return types.filter((part) => part.kind !== typescript_1.default.SyntaxKind.NullKeyword && part.kind !== typescript_1.default.SyntaxKind.UndefinedKeyword);
|
|
36
|
+
}
|
|
37
|
+
function collectFiniteStringLiterals(node) {
|
|
38
|
+
const values = [];
|
|
39
|
+
for (const part of node.types) {
|
|
40
|
+
if (!typescript_1.default.isLiteralTypeNode(part) || !typescript_1.default.isStringLiteral(part.literal))
|
|
41
|
+
return null;
|
|
42
|
+
values.push(part.literal.text);
|
|
43
|
+
}
|
|
44
|
+
return values;
|
|
45
|
+
}
|
|
46
|
+
function collectFiniteBooleanLiterals(node) {
|
|
47
|
+
const values = [];
|
|
48
|
+
for (const part of node.types) {
|
|
49
|
+
if (!typescript_1.default.isLiteralTypeNode(part))
|
|
50
|
+
return null;
|
|
51
|
+
if (part.literal.kind === typescript_1.default.SyntaxKind.TrueKeyword) {
|
|
52
|
+
values.push(true);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (part.literal.kind === typescript_1.default.SyntaxKind.FalseKeyword) {
|
|
56
|
+
values.push(false);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return values;
|
|
62
|
+
}
|
|
63
|
+
function collectFiniteNumberLiterals(node) {
|
|
64
|
+
const values = [];
|
|
65
|
+
for (const part of node.types) {
|
|
66
|
+
if (!typescript_1.default.isLiteralTypeNode(part) || !typescript_1.default.isNumericLiteral(part.literal))
|
|
67
|
+
return null;
|
|
68
|
+
values.push(Number(part.literal.text));
|
|
69
|
+
}
|
|
70
|
+
return values;
|
|
71
|
+
}
|
|
72
|
+
function finiteDomainSymbolForValue(value) {
|
|
73
|
+
if (typeof value === "boolean")
|
|
74
|
+
return value ? "True" : "False";
|
|
75
|
+
if (typeof value === "number") {
|
|
76
|
+
const text = Number.isInteger(value) ? `${value}` : `${value}`.replace(/\./g, "_");
|
|
77
|
+
return (0, boundary_codec_model_1.sanitizeIdentifier)(`Value_${text.replace(/-/g, "neg_")}`);
|
|
78
|
+
}
|
|
79
|
+
const trimmed = value.trim();
|
|
80
|
+
if (trimmed.length === 0)
|
|
81
|
+
return "Empty";
|
|
82
|
+
const direct = (0, boundary_codec_model_1.sanitizeIdentifier)(trimmed);
|
|
83
|
+
return direct.length > 0 ? direct : "Value";
|
|
84
|
+
}
|
|
85
|
+
function buildFiniteDomain(primitive, values) {
|
|
86
|
+
const seen = new Set();
|
|
87
|
+
const variants = [];
|
|
88
|
+
for (const value of values) {
|
|
89
|
+
const key = `${typeof value}:${String(value)}`;
|
|
90
|
+
if (seen.has(key))
|
|
91
|
+
continue;
|
|
92
|
+
seen.add(key);
|
|
93
|
+
variants.push({
|
|
94
|
+
code: variants.length,
|
|
95
|
+
symbolicName: finiteDomainSymbolForValue(value),
|
|
96
|
+
tsLiteralText: typeof value === "string" ? JSON.stringify(value) : `${value}`,
|
|
97
|
+
value
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return { primitive, variants };
|
|
101
|
+
}
|
|
102
|
+
function unsupported(path, typeText, reason) {
|
|
103
|
+
throw new errors_1.VerifyError(`Boundary codec planning failed for '${path.join(".") || typeText}': ${reason} (${typeText}).`);
|
|
104
|
+
}
|
|
105
|
+
class BoundaryTransportAnalyzer {
|
|
106
|
+
constructor(spec) {
|
|
107
|
+
this.spec = spec;
|
|
108
|
+
this.declNodes = new Map();
|
|
109
|
+
this.namedNodes = new Map();
|
|
110
|
+
for (const decl of this.collectDecls()) {
|
|
111
|
+
const node = (0, boundary_codec_model_1.parseTypeDeclNode)(decl.nodeText);
|
|
112
|
+
if (node) {
|
|
113
|
+
this.declNodes.set(decl.name, node);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
analyzeTypeText(typeText, path) {
|
|
118
|
+
const rootNode = (0, boundary_codec_model_1.parseTypeNodeFromText)(typeText);
|
|
119
|
+
const root = this.resolveTypeNode(rootNode, typeText, path, [], this.defaultIdentityParts(rootNode, typeText, path));
|
|
120
|
+
return {
|
|
121
|
+
typeText,
|
|
122
|
+
tsTypeText: (0, boundary_codec_model_1.stripAnQstType)(typeText),
|
|
123
|
+
root,
|
|
124
|
+
summary: summarizeAnalysis(root)
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
collectDecls() {
|
|
128
|
+
const out = new Map();
|
|
129
|
+
for (const decl of this.spec.namespaceTypeDecls)
|
|
130
|
+
out.set(decl.name, decl);
|
|
131
|
+
for (const decl of this.spec.importedTypeDecls.values())
|
|
132
|
+
out.set(decl.name, decl);
|
|
133
|
+
return [...out.values()];
|
|
134
|
+
}
|
|
135
|
+
nodeMeta(typeText, path, identityParts) {
|
|
136
|
+
const normalizedParts = identityParts.filter((part) => part.trim().length > 0);
|
|
137
|
+
return {
|
|
138
|
+
typeText,
|
|
139
|
+
path,
|
|
140
|
+
typeIdentityKey: normalizedParts.join("::") || (0, boundary_codec_model_1.stripAnQstType)(typeText).trim(),
|
|
141
|
+
cppNameHintParts: normalizedParts.length > 0 ? normalizedParts : (path.length > 0 ? path : [(0, boundary_codec_model_1.stripAnQstType)(typeText).trim() || "AnonymousType"])
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
defaultIdentityParts(node, typeText, path) {
|
|
145
|
+
if (typescript_1.default.isTypeReferenceNode(node)) {
|
|
146
|
+
const name = (0, boundary_codec_model_1.qNameText)(node.typeName);
|
|
147
|
+
if (!name.startsWith("AnQst.Type.") && !BUILTIN_TYPE_REFS.has(name)) {
|
|
148
|
+
return [name];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return path.length > 0 ? [...path] : [(0, boundary_codec_model_1.stripAnQstType)(typeText).trim() || "AnonymousType"];
|
|
152
|
+
}
|
|
153
|
+
createFiniteDomainAnalysis(typeText, path, identityParts, domain) {
|
|
154
|
+
return {
|
|
155
|
+
nodeKind: "finite-domain",
|
|
156
|
+
...this.nodeMeta(typeText, path, identityParts),
|
|
157
|
+
domain
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
createStructAnalysis(typeText, path, members, stack, identityParts) {
|
|
161
|
+
const fields = members
|
|
162
|
+
.filter((member) => {
|
|
163
|
+
return typescript_1.default.isPropertySignature(member) && !!member.type && typescript_1.default.isIdentifier(member.name);
|
|
164
|
+
})
|
|
165
|
+
.map((member) => {
|
|
166
|
+
const fieldPath = [...path, member.name.text];
|
|
167
|
+
const fieldIdentityParts = [...identityParts, member.name.text];
|
|
168
|
+
const child = this.resolveTypeNode(member.type, member.type.getText(), fieldPath, stack, fieldIdentityParts);
|
|
169
|
+
return {
|
|
170
|
+
name: member.name.text,
|
|
171
|
+
optional: !!member.questionToken,
|
|
172
|
+
typeText: member.type.getText(),
|
|
173
|
+
path: fieldPath,
|
|
174
|
+
typeIdentityKey: child.typeIdentityKey,
|
|
175
|
+
cppNameHintParts: child.cppNameHintParts,
|
|
176
|
+
reconstructionKey: member.name.text,
|
|
177
|
+
node: child
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
return {
|
|
181
|
+
nodeKind: "struct",
|
|
182
|
+
...this.nodeMeta(typeText, path, identityParts),
|
|
183
|
+
fields,
|
|
184
|
+
reconstruction: "object"
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
resolveNamedReference(name, decl) {
|
|
188
|
+
const existing = this.namedNodes.get(name);
|
|
189
|
+
if (existing) {
|
|
190
|
+
return existing;
|
|
191
|
+
}
|
|
192
|
+
const placeholder = {
|
|
193
|
+
nodeKind: "named",
|
|
194
|
+
...this.nodeMeta(name, [name], [name]),
|
|
195
|
+
name,
|
|
196
|
+
target: null
|
|
197
|
+
};
|
|
198
|
+
this.namedNodes.set(name, placeholder);
|
|
199
|
+
placeholder.target = typescript_1.default.isInterfaceDeclaration(decl)
|
|
200
|
+
? this.createStructAnalysis(name, [name], decl.members, [name], [name])
|
|
201
|
+
: this.resolveTypeNode(decl.type, name, [name], [name], [name]);
|
|
202
|
+
return placeholder;
|
|
203
|
+
}
|
|
204
|
+
resolveTypeNode(node, typeText, path, stack, identityParts) {
|
|
205
|
+
if (typescript_1.default.isParenthesizedTypeNode(node)) {
|
|
206
|
+
return this.resolveTypeNode(node.type, typeText, path, stack, identityParts);
|
|
207
|
+
}
|
|
208
|
+
if (typescript_1.default.isTypeLiteralNode(node)) {
|
|
209
|
+
return this.createStructAnalysis(typeText, path, node.members, stack, identityParts);
|
|
210
|
+
}
|
|
211
|
+
if (typescript_1.default.isArrayTypeNode(node)) {
|
|
212
|
+
return {
|
|
213
|
+
nodeKind: "array",
|
|
214
|
+
...this.nodeMeta(typeText, path, identityParts),
|
|
215
|
+
elementTypeText: node.elementType.getText(),
|
|
216
|
+
element: this.resolveTypeNode(node.elementType, node.elementType.getText(), [...path, "Item"], stack, [...identityParts, "Item"]),
|
|
217
|
+
requiresCountMetadata: true,
|
|
218
|
+
reconstruction: "array"
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (typescript_1.default.isTupleTypeNode(node)) {
|
|
222
|
+
unsupported(path, typeText, "tuple transport is not supported by the whole-boundary planner");
|
|
223
|
+
}
|
|
224
|
+
if (typescript_1.default.isLiteralTypeNode(node)) {
|
|
225
|
+
if (typescript_1.default.isStringLiteral(node.literal)) {
|
|
226
|
+
return this.createFiniteDomainAnalysis(typeText, path, identityParts, buildFiniteDomain("string", [node.literal.text]));
|
|
227
|
+
}
|
|
228
|
+
if (typescript_1.default.isNumericLiteral(node.literal)) {
|
|
229
|
+
return this.createFiniteDomainAnalysis(typeText, path, identityParts, buildFiniteDomain("number", [Number(node.literal.text)]));
|
|
230
|
+
}
|
|
231
|
+
if (node.literal.kind === typescript_1.default.SyntaxKind.TrueKeyword || node.literal.kind === typescript_1.default.SyntaxKind.FalseKeyword) {
|
|
232
|
+
return this.createFiniteDomainAnalysis(typeText, path, identityParts, buildFiniteDomain("boolean", [node.literal.kind === typescript_1.default.SyntaxKind.TrueKeyword]));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (typescript_1.default.isUnionTypeNode(node)) {
|
|
236
|
+
const filtered = filterNullishUnionParts(node.types);
|
|
237
|
+
if (filtered.length !== node.types.length) {
|
|
238
|
+
unsupported(path, typeText, "nullish unions are not supported; use explicit optional members instead");
|
|
239
|
+
}
|
|
240
|
+
const finiteStringVariants = collectFiniteStringLiterals(node);
|
|
241
|
+
if (finiteStringVariants) {
|
|
242
|
+
return this.createFiniteDomainAnalysis(typeText, path, identityParts, buildFiniteDomain("string", finiteStringVariants));
|
|
243
|
+
}
|
|
244
|
+
const finiteBooleanVariants = collectFiniteBooleanLiterals(node);
|
|
245
|
+
if (finiteBooleanVariants) {
|
|
246
|
+
return this.createFiniteDomainAnalysis(typeText, path, identityParts, buildFiniteDomain("boolean", finiteBooleanVariants));
|
|
247
|
+
}
|
|
248
|
+
const finiteNumberVariants = collectFiniteNumberLiterals(node);
|
|
249
|
+
if (finiteNumberVariants) {
|
|
250
|
+
return this.createFiniteDomainAnalysis(typeText, path, identityParts, buildFiniteDomain("number", finiteNumberVariants));
|
|
251
|
+
}
|
|
252
|
+
if (isStringLikeUnion(node)) {
|
|
253
|
+
return {
|
|
254
|
+
nodeKind: "leaf",
|
|
255
|
+
...this.nodeMeta(typeText, path, identityParts),
|
|
256
|
+
leaf: (0, boundary_codec_leaves_1.resolveLeafCapability)("string", "string"),
|
|
257
|
+
fixedWidth: false
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
if (isBooleanLikeUnion(node)) {
|
|
261
|
+
return {
|
|
262
|
+
nodeKind: "leaf",
|
|
263
|
+
...this.nodeMeta(typeText, path, identityParts),
|
|
264
|
+
leaf: (0, boundary_codec_leaves_1.resolveLeafCapability)("boolean", "boolean"),
|
|
265
|
+
fixedWidth: true
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (isNumberLikeUnion(node)) {
|
|
269
|
+
return {
|
|
270
|
+
nodeKind: "leaf",
|
|
271
|
+
...this.nodeMeta(typeText, path, identityParts),
|
|
272
|
+
leaf: (0, boundary_codec_leaves_1.resolveLeafCapability)("number", "number"),
|
|
273
|
+
fixedWidth: true
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
unsupported(path, typeText, "union transport is only supported for string, boolean, or number-like unions");
|
|
277
|
+
}
|
|
278
|
+
if (typescript_1.default.isTypeReferenceNode(node)) {
|
|
279
|
+
const name = (0, boundary_codec_model_1.qNameText)(node.typeName);
|
|
280
|
+
const rawText = node.getText();
|
|
281
|
+
if (name === "Array" || name === "ReadonlyArray") {
|
|
282
|
+
const arg = node.typeArguments?.[0];
|
|
283
|
+
if (!arg) {
|
|
284
|
+
unsupported(path, rawText, "array type is missing its element type");
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
nodeKind: "array",
|
|
288
|
+
...this.nodeMeta(typeText, path, identityParts),
|
|
289
|
+
elementTypeText: arg.getText(),
|
|
290
|
+
element: this.resolveTypeNode(arg, arg.getText(), [...path, "Item"], stack, [...identityParts, "Item"]),
|
|
291
|
+
requiresCountMetadata: true,
|
|
292
|
+
reconstruction: "array"
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
if (name === "Record" || name === "Map") {
|
|
296
|
+
const leaf = (0, boundary_codec_leaves_1.resolveLeafCapability)("object", "object");
|
|
297
|
+
return {
|
|
298
|
+
nodeKind: "leaf",
|
|
299
|
+
...this.nodeMeta(typeText, path, identityParts),
|
|
300
|
+
leaf: leaf,
|
|
301
|
+
fixedWidth: false
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
if (name === "Partial") {
|
|
305
|
+
unsupported(path, rawText, "generic Partial<T> transport is not supported");
|
|
306
|
+
}
|
|
307
|
+
if (name === "Promise") {
|
|
308
|
+
unsupported(path, rawText, "Promise transport is not supported");
|
|
309
|
+
}
|
|
310
|
+
if (rawText.trim() === "AnQst.Type.stringArray") {
|
|
311
|
+
const leaf = (0, boundary_codec_leaves_1.resolveLeafCapability)("string", "string");
|
|
312
|
+
return {
|
|
313
|
+
nodeKind: "array",
|
|
314
|
+
...this.nodeMeta(typeText, path, identityParts),
|
|
315
|
+
elementTypeText: "string",
|
|
316
|
+
element: {
|
|
317
|
+
nodeKind: "leaf",
|
|
318
|
+
...this.nodeMeta("string", [...path, "Item"], [...identityParts, "Item"]),
|
|
319
|
+
leaf: leaf,
|
|
320
|
+
fixedWidth: false
|
|
321
|
+
},
|
|
322
|
+
requiresCountMetadata: true,
|
|
323
|
+
reconstruction: "array"
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
const leaf = (0, boundary_codec_leaves_1.resolveLeafCapability)(rawText, name);
|
|
327
|
+
if (leaf) {
|
|
328
|
+
return {
|
|
329
|
+
nodeKind: "leaf",
|
|
330
|
+
...this.nodeMeta(typeText, path, identityParts),
|
|
331
|
+
leaf,
|
|
332
|
+
fixedWidth: leaf.fixedByteWidth !== null
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
const decl = this.declNodes.get(name);
|
|
336
|
+
if (decl) {
|
|
337
|
+
return this.resolveNamedReference(name, decl);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
switch (node.kind) {
|
|
341
|
+
case typescript_1.default.SyntaxKind.StringKeyword:
|
|
342
|
+
return {
|
|
343
|
+
nodeKind: "leaf",
|
|
344
|
+
...this.nodeMeta(typeText, path, identityParts),
|
|
345
|
+
leaf: (0, boundary_codec_leaves_1.resolveLeafCapability)("string", "string"),
|
|
346
|
+
fixedWidth: false
|
|
347
|
+
};
|
|
348
|
+
case typescript_1.default.SyntaxKind.BooleanKeyword:
|
|
349
|
+
return {
|
|
350
|
+
nodeKind: "leaf",
|
|
351
|
+
...this.nodeMeta(typeText, path, identityParts),
|
|
352
|
+
leaf: (0, boundary_codec_leaves_1.resolveLeafCapability)("boolean", "boolean"),
|
|
353
|
+
fixedWidth: true
|
|
354
|
+
};
|
|
355
|
+
case typescript_1.default.SyntaxKind.NumberKeyword:
|
|
356
|
+
return {
|
|
357
|
+
nodeKind: "leaf",
|
|
358
|
+
...this.nodeMeta(typeText, path, identityParts),
|
|
359
|
+
leaf: (0, boundary_codec_leaves_1.resolveLeafCapability)("number", "number"),
|
|
360
|
+
fixedWidth: true
|
|
361
|
+
};
|
|
362
|
+
case typescript_1.default.SyntaxKind.BigIntKeyword:
|
|
363
|
+
return {
|
|
364
|
+
nodeKind: "leaf",
|
|
365
|
+
...this.nodeMeta(typeText, path, identityParts),
|
|
366
|
+
leaf: (0, boundary_codec_leaves_1.resolveLeafCapability)("bigint", "bigint"),
|
|
367
|
+
fixedWidth: true
|
|
368
|
+
};
|
|
369
|
+
case typescript_1.default.SyntaxKind.ObjectKeyword:
|
|
370
|
+
return {
|
|
371
|
+
nodeKind: "leaf",
|
|
372
|
+
...this.nodeMeta(typeText, path, identityParts),
|
|
373
|
+
leaf: (0, boundary_codec_leaves_1.resolveLeafCapability)("object", "object"),
|
|
374
|
+
fixedWidth: false
|
|
375
|
+
};
|
|
376
|
+
default:
|
|
377
|
+
unsupported(path, typeText, "no transport analysis rule exists for this type");
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
exports.BoundaryTransportAnalyzer = BoundaryTransportAnalyzer;
|
|
382
|
+
function summarizeAnalysis(node) {
|
|
383
|
+
const used = new Set();
|
|
384
|
+
const visitedNamed = new Set();
|
|
385
|
+
const visit = (current) => {
|
|
386
|
+
switch (current.nodeKind) {
|
|
387
|
+
case "leaf":
|
|
388
|
+
used.add(current.leaf.key);
|
|
389
|
+
return {
|
|
390
|
+
hasBlobLeaves: current.leaf.region === "blob",
|
|
391
|
+
hasStringLeaves: current.leaf.region === "string",
|
|
392
|
+
hasBinaryLeaves: current.leaf.region === "binary",
|
|
393
|
+
hasDynamicLeaves: current.leaf.region === "dynamic",
|
|
394
|
+
hasRepeatedStructures: false,
|
|
395
|
+
hasOptionalPresence: false,
|
|
396
|
+
hasFiniteDomains: false,
|
|
397
|
+
usedLeafCapabilities: []
|
|
398
|
+
};
|
|
399
|
+
case "named":
|
|
400
|
+
if (visitedNamed.has(current.name)) {
|
|
401
|
+
return {
|
|
402
|
+
hasBlobLeaves: false,
|
|
403
|
+
hasStringLeaves: false,
|
|
404
|
+
hasBinaryLeaves: false,
|
|
405
|
+
hasDynamicLeaves: false,
|
|
406
|
+
hasRepeatedStructures: false,
|
|
407
|
+
hasOptionalPresence: false,
|
|
408
|
+
hasFiniteDomains: false,
|
|
409
|
+
usedLeafCapabilities: []
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
visitedNamed.add(current.name);
|
|
413
|
+
return visit(current.target);
|
|
414
|
+
case "finite-domain":
|
|
415
|
+
return {
|
|
416
|
+
hasBlobLeaves: false,
|
|
417
|
+
hasStringLeaves: false,
|
|
418
|
+
hasBinaryLeaves: false,
|
|
419
|
+
hasDynamicLeaves: false,
|
|
420
|
+
hasRepeatedStructures: false,
|
|
421
|
+
hasOptionalPresence: false,
|
|
422
|
+
hasFiniteDomains: true,
|
|
423
|
+
usedLeafCapabilities: []
|
|
424
|
+
};
|
|
425
|
+
case "array": {
|
|
426
|
+
const inner = visit(current.element);
|
|
427
|
+
return {
|
|
428
|
+
hasBlobLeaves: inner.hasBlobLeaves,
|
|
429
|
+
hasStringLeaves: inner.hasStringLeaves,
|
|
430
|
+
hasBinaryLeaves: inner.hasBinaryLeaves,
|
|
431
|
+
hasDynamicLeaves: inner.hasDynamicLeaves,
|
|
432
|
+
hasRepeatedStructures: true,
|
|
433
|
+
hasOptionalPresence: inner.hasOptionalPresence,
|
|
434
|
+
hasFiniteDomains: inner.hasFiniteDomains,
|
|
435
|
+
usedLeafCapabilities: []
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
case "struct":
|
|
439
|
+
return current.fields.reduce((acc, field) => {
|
|
440
|
+
const next = visit(field.node);
|
|
441
|
+
return {
|
|
442
|
+
hasBlobLeaves: acc.hasBlobLeaves || next.hasBlobLeaves,
|
|
443
|
+
hasStringLeaves: acc.hasStringLeaves || next.hasStringLeaves,
|
|
444
|
+
hasBinaryLeaves: acc.hasBinaryLeaves || next.hasBinaryLeaves,
|
|
445
|
+
hasDynamicLeaves: acc.hasDynamicLeaves || next.hasDynamicLeaves,
|
|
446
|
+
hasRepeatedStructures: acc.hasRepeatedStructures || next.hasRepeatedStructures,
|
|
447
|
+
hasOptionalPresence: acc.hasOptionalPresence || field.optional || next.hasOptionalPresence,
|
|
448
|
+
hasFiniteDomains: acc.hasFiniteDomains || next.hasFiniteDomains,
|
|
449
|
+
usedLeafCapabilities: []
|
|
450
|
+
};
|
|
451
|
+
}, {
|
|
452
|
+
hasBlobLeaves: false,
|
|
453
|
+
hasStringLeaves: false,
|
|
454
|
+
hasBinaryLeaves: false,
|
|
455
|
+
hasDynamicLeaves: false,
|
|
456
|
+
hasRepeatedStructures: false,
|
|
457
|
+
hasOptionalPresence: false,
|
|
458
|
+
hasFiniteDomains: false,
|
|
459
|
+
usedLeafCapabilities: []
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
const summary = visit(node);
|
|
464
|
+
return {
|
|
465
|
+
...summary,
|
|
466
|
+
usedLeafCapabilities: [...used]
|
|
467
|
+
};
|
|
468
|
+
}
|