@dusted/anqst 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -117
- package/dist/src/app.js +180 -272
- package/dist/src/build-stamp.js +5 -0
- package/dist/src/{backend/tsc/debug-dump.js → debug-dump.js} +2 -1
- package/dist/src/emit.js +368 -170
- package/dist/src/layout.js +70 -0
- package/dist/src/parser.js +124 -6
- package/dist/src/{backend/tsc/program.js → program.js} +1 -1
- package/dist/src/project.js +220 -137
- package/dist/src/verify.js +15 -2
- package/index.d.ts +1 -0
- package/package.json +7 -2
- package/spec/AnQst-Spec-DSL.d.ts +49 -17
- package/dist/src/backend/ast/emit.js +0 -5
- package/dist/src/backend/ast/index.js +0 -13
- package/dist/src/backend/ast/parser.js +0 -5
- package/dist/src/backend/ast/verify.js +0 -5
- package/dist/src/backend/index.js +0 -16
- package/dist/src/backend/tsc/emit-cpp.js +0 -13
- package/dist/src/backend/tsc/emit-node.js +0 -13
- package/dist/src/backend/tsc/index.js +0 -41
- package/dist/src/backend/tsc/parser.js +0 -19
- package/dist/src/backend/tsc/verify.js +0 -13
- package/dist/src/backend/types.js +0 -2
- /package/dist/src/{backend/tsc/typegraph.js → typegraph.js} +0 -0
|
@@ -0,0 +1,70 @@
|
|
|
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.ANQST_LAYOUT_VERSION = exports.ANQST_GENERATED_DIRNAME = exports.ANQST_ROOT_DIRNAME = void 0;
|
|
7
|
+
exports.normalizeSlashes = normalizeSlashes;
|
|
8
|
+
exports.anqstRootDir = anqstRootDir;
|
|
9
|
+
exports.anqstGeneratedRootDir = anqstGeneratedRootDir;
|
|
10
|
+
exports.anqstDebugIntermediateRootDir = anqstDebugIntermediateRootDir;
|
|
11
|
+
exports.anqstSpecFileName = anqstSpecFileName;
|
|
12
|
+
exports.anqstSettingsFileName = anqstSettingsFileName;
|
|
13
|
+
exports.anqstSettingsRelativePath = anqstSettingsRelativePath;
|
|
14
|
+
exports.generatedFrontendDirName = generatedFrontendDirName;
|
|
15
|
+
exports.generatedNodeExpressDirName = generatedNodeExpressDirName;
|
|
16
|
+
exports.generatedQtWidgetDirName = generatedQtWidgetDirName;
|
|
17
|
+
exports.resolveGeneratedLayoutPaths = resolveGeneratedLayoutPaths;
|
|
18
|
+
exports.toProjectRelative = toProjectRelative;
|
|
19
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
20
|
+
exports.ANQST_ROOT_DIRNAME = "AnQst";
|
|
21
|
+
exports.ANQST_GENERATED_DIRNAME = "generated";
|
|
22
|
+
exports.ANQST_LAYOUT_VERSION = 2;
|
|
23
|
+
function normalizeSlashes(inputPath) {
|
|
24
|
+
return inputPath.split(node_path_1.default.sep).join("/");
|
|
25
|
+
}
|
|
26
|
+
function anqstRootDir(cwd) {
|
|
27
|
+
return node_path_1.default.join(cwd, exports.ANQST_ROOT_DIRNAME);
|
|
28
|
+
}
|
|
29
|
+
function anqstGeneratedRootDir(cwd) {
|
|
30
|
+
return node_path_1.default.join(anqstRootDir(cwd), exports.ANQST_GENERATED_DIRNAME);
|
|
31
|
+
}
|
|
32
|
+
function anqstDebugIntermediateRootDir(cwd) {
|
|
33
|
+
return node_path_1.default.join(anqstGeneratedRootDir(cwd), "debug", "intermediate");
|
|
34
|
+
}
|
|
35
|
+
function anqstSpecFileName(widgetName) {
|
|
36
|
+
return `${widgetName}.AnQst.d.ts`;
|
|
37
|
+
}
|
|
38
|
+
function anqstSettingsFileName(widgetName) {
|
|
39
|
+
return `${widgetName}.settings.json`;
|
|
40
|
+
}
|
|
41
|
+
function anqstSettingsRelativePath(widgetName) {
|
|
42
|
+
return `./${exports.ANQST_ROOT_DIRNAME}/${anqstSettingsFileName(widgetName)}`;
|
|
43
|
+
}
|
|
44
|
+
function generatedFrontendDirName(widgetName) {
|
|
45
|
+
return `${widgetName}_Angular`;
|
|
46
|
+
}
|
|
47
|
+
function generatedNodeExpressDirName(widgetName) {
|
|
48
|
+
return `${widgetName}_anQst`;
|
|
49
|
+
}
|
|
50
|
+
function generatedQtWidgetDirName(widgetName) {
|
|
51
|
+
return `${widgetName}_widget`;
|
|
52
|
+
}
|
|
53
|
+
function resolveGeneratedLayoutPaths(cwd, widgetName) {
|
|
54
|
+
const generatedRoot = anqstGeneratedRootDir(cwd);
|
|
55
|
+
const cppQtWidgetRoot = node_path_1.default.join(generatedRoot, "backend", "cpp", "qt", generatedQtWidgetDirName(widgetName));
|
|
56
|
+
const designerPluginRoot = node_path_1.default.join(cppQtWidgetRoot, "designerPlugin");
|
|
57
|
+
return {
|
|
58
|
+
generatedRoot,
|
|
59
|
+
frontendRoot: node_path_1.default.join(generatedRoot, "frontend", generatedFrontendDirName(widgetName)),
|
|
60
|
+
nodeExpressRoot: node_path_1.default.join(generatedRoot, "backend", "node", "express", generatedNodeExpressDirName(widgetName)),
|
|
61
|
+
cppCmakeRoot: node_path_1.default.join(generatedRoot, "backend", "cpp", "cmake"),
|
|
62
|
+
cppQtWidgetRoot,
|
|
63
|
+
designerPluginRoot,
|
|
64
|
+
designerPluginBuildRoot: node_path_1.default.join(designerPluginRoot, "build"),
|
|
65
|
+
debugIntermediateRoot: anqstDebugIntermediateRootDir(cwd)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function toProjectRelative(cwd, absolutePath) {
|
|
69
|
+
return normalizeSlashes(node_path_1.default.relative(cwd, absolutePath));
|
|
70
|
+
}
|
package/dist/src/parser.js
CHANGED
|
@@ -8,6 +8,9 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const typescript_1 = __importDefault(require("typescript"));
|
|
10
10
|
const errors_1 = require("./errors");
|
|
11
|
+
const program_1 = require("./program");
|
|
12
|
+
const typegraph_1 = require("./typegraph");
|
|
13
|
+
const debug_dump_1 = require("./debug-dump");
|
|
11
14
|
function locFromNode(source, node) {
|
|
12
15
|
const lc = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
13
16
|
return {
|
|
@@ -59,19 +62,116 @@ function parseMemberKindFromAnQstType(typeNode) {
|
|
|
59
62
|
const arg = typeNode.typeArguments?.[0];
|
|
60
63
|
return { kind, payload: arg ? arg.getText() : null };
|
|
61
64
|
}
|
|
62
|
-
|
|
65
|
+
const DEFAULT_MEMBER_TIMEOUT_MS = 120000;
|
|
66
|
+
const MAX_MEMBER_TIMEOUT_MS = 2147483647;
|
|
67
|
+
function parseMemberKindWithConfig(typeNode) {
|
|
68
|
+
if (!typescript_1.default.isTypeReferenceNode(typeNode))
|
|
69
|
+
return null;
|
|
70
|
+
const typeName = qNameToText(typeNode.typeName);
|
|
71
|
+
if (!typeName.startsWith("AnQst."))
|
|
72
|
+
return null;
|
|
73
|
+
const kind = typeName.slice("AnQst.".length);
|
|
74
|
+
if (!["Call", "Slot", "Emitter", "Output", "Input"].includes(kind))
|
|
75
|
+
return null;
|
|
76
|
+
const typeArgs = typeNode.typeArguments ?? [];
|
|
77
|
+
if (kind === "Emitter") {
|
|
78
|
+
return {
|
|
79
|
+
kind,
|
|
80
|
+
payload: null,
|
|
81
|
+
configTypeNode: typeArgs[0] ?? null
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
kind,
|
|
86
|
+
payload: typeArgs[0] ? typeArgs[0].getText() : null,
|
|
87
|
+
configTypeNode: kind === "Call" ? (typeArgs[1] ?? null) : null
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function parseNumericLiteralType(node) {
|
|
91
|
+
if (!typescript_1.default.isLiteralTypeNode(node))
|
|
92
|
+
return null;
|
|
93
|
+
if (typescript_1.default.isNumericLiteral(node.literal)) {
|
|
94
|
+
return Number(node.literal.text);
|
|
95
|
+
}
|
|
96
|
+
if (typescript_1.default.isPrefixUnaryExpression(node.literal)
|
|
97
|
+
&& node.literal.operator === typescript_1.default.SyntaxKind.MinusToken
|
|
98
|
+
&& typescript_1.default.isNumericLiteral(node.literal.operand)) {
|
|
99
|
+
return -Number(node.literal.operand.text);
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
function resolveMemberTimeoutMs(source, serviceName, memberName, kind, configTypeNode, warnings, memberLoc) {
|
|
104
|
+
if (kind !== "Call")
|
|
105
|
+
return DEFAULT_MEMBER_TIMEOUT_MS;
|
|
106
|
+
if (!configTypeNode)
|
|
107
|
+
return DEFAULT_MEMBER_TIMEOUT_MS;
|
|
108
|
+
if (!typescript_1.default.isTypeLiteralNode(configTypeNode)) {
|
|
109
|
+
throw new errors_1.VerifyError(`${kind} config for '${memberName}' must be an inline object literal type.`, locFromNode(source, configTypeNode));
|
|
110
|
+
}
|
|
111
|
+
let timeoutSeconds = null;
|
|
112
|
+
let timeoutMilliseconds = null;
|
|
113
|
+
const memberPath = `${serviceName}.${memberName}`;
|
|
114
|
+
for (const prop of configTypeNode.members) {
|
|
115
|
+
if (!typescript_1.default.isPropertySignature(prop) || !prop.name) {
|
|
116
|
+
throw new errors_1.VerifyError(`${kind} config for '${memberName}' only supports named properties.`, locFromNode(source, prop));
|
|
117
|
+
}
|
|
118
|
+
if (!typescript_1.default.isIdentifier(prop.name)) {
|
|
119
|
+
throw new errors_1.VerifyError(`${kind} config for '${memberName}' only supports identifier keys.`, locFromNode(source, prop.name));
|
|
120
|
+
}
|
|
121
|
+
const key = prop.name.text;
|
|
122
|
+
if (!prop.type) {
|
|
123
|
+
throw new errors_1.VerifyError(`${kind} config key '${key}' in '${memberName}' must declare a numeric literal value.`, locFromNode(source, prop));
|
|
124
|
+
}
|
|
125
|
+
if (key !== "timeoutSeconds" && key !== "timeoutMilliseconds") {
|
|
126
|
+
warnings.push({
|
|
127
|
+
severity: "warn",
|
|
128
|
+
message: `Unknown ${kind} config key '${key}' ignored for '${memberPath}'.`,
|
|
129
|
+
loc: locFromNode(source, prop.name),
|
|
130
|
+
memberPath
|
|
131
|
+
});
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const numericValue = parseNumericLiteralType(prop.type);
|
|
135
|
+
if (numericValue === null || !Number.isInteger(numericValue)) {
|
|
136
|
+
throw new errors_1.VerifyError(`${kind} config key '${key}' in '${memberName}' must be an integer literal >= 0.`, locFromNode(source, prop.type));
|
|
137
|
+
}
|
|
138
|
+
if (numericValue < 0) {
|
|
139
|
+
throw new errors_1.VerifyError(`${kind} config key '${key}' in '${memberName}' must be >= 0.`, locFromNode(source, prop.type));
|
|
140
|
+
}
|
|
141
|
+
if (key === "timeoutSeconds")
|
|
142
|
+
timeoutSeconds = numericValue;
|
|
143
|
+
if (key === "timeoutMilliseconds")
|
|
144
|
+
timeoutMilliseconds = numericValue;
|
|
145
|
+
}
|
|
146
|
+
if (timeoutSeconds !== null && timeoutMilliseconds !== null) {
|
|
147
|
+
throw new errors_1.VerifyError(`${kind} config for '${memberName}' must specify only one of 'timeoutSeconds' or 'timeoutMilliseconds'.`, memberLoc);
|
|
148
|
+
}
|
|
149
|
+
const effectiveMs = timeoutMilliseconds !== null
|
|
150
|
+
? timeoutMilliseconds
|
|
151
|
+
: timeoutSeconds !== null
|
|
152
|
+
? timeoutSeconds * 1000
|
|
153
|
+
: DEFAULT_MEMBER_TIMEOUT_MS;
|
|
154
|
+
if (effectiveMs > MAX_MEMBER_TIMEOUT_MS) {
|
|
155
|
+
throw new errors_1.VerifyError(`${kind} timeout for '${memberName}' exceeds max supported value (${MAX_MEMBER_TIMEOUT_MS} ms).`, memberLoc);
|
|
156
|
+
}
|
|
157
|
+
return effectiveMs;
|
|
158
|
+
}
|
|
159
|
+
function parseServiceMember(source, serviceName, member, warnings) {
|
|
63
160
|
if (typescript_1.default.isMethodSignature(member)) {
|
|
64
161
|
if (member.questionToken)
|
|
65
162
|
throw new errors_1.VerifyError("Optional service methods are not allowed.", locFromNode(source, member));
|
|
66
163
|
const returnType = member.type;
|
|
67
164
|
if (!returnType)
|
|
68
165
|
throw new errors_1.VerifyError("Service method must declare return type.", locFromNode(source, member));
|
|
69
|
-
const parsed =
|
|
166
|
+
const parsed = parseMemberKindWithConfig(returnType);
|
|
70
167
|
if (!parsed)
|
|
71
168
|
throw new errors_1.VerifyError(`Unsupported service method return type '${returnType.getText()}'.`, locFromNode(source, member));
|
|
72
169
|
if (parsed.kind === "Input" || parsed.kind === "Output") {
|
|
73
170
|
throw new errors_1.VerifyError(`${parsed.kind} must be declared as property, not method.`, locFromNode(source, member));
|
|
74
171
|
}
|
|
172
|
+
if (parsed.kind === "Emitter" && parsed.configTypeNode !== null) {
|
|
173
|
+
throw new errors_1.VerifyError(`Emitter '${member.name.getText(source)}' does not support config parameters; use plain AnQst.Emitter.`, locFromNode(source, parsed.configTypeNode));
|
|
174
|
+
}
|
|
75
175
|
if (!member.name || !typescript_1.default.isIdentifier(member.name)) {
|
|
76
176
|
throw new errors_1.VerifyError("Only identifier service method names are supported.", locFromNode(source, member));
|
|
77
177
|
}
|
|
@@ -84,11 +184,13 @@ function parseServiceMember(source, member) {
|
|
|
84
184
|
throw new errors_1.VerifyError("Service parameters must declare type.", locFromNode(source, param));
|
|
85
185
|
return { name: param.name.text, typeText: param.type.getText() };
|
|
86
186
|
});
|
|
187
|
+
const timeoutMs = resolveMemberTimeoutMs(source, serviceName, member.name.text, parsed.kind, parsed.configTypeNode, warnings, locFromNode(source, member));
|
|
87
188
|
return {
|
|
88
189
|
kind: parsed.kind,
|
|
89
190
|
name: member.name.text,
|
|
90
191
|
payloadTypeText: parsed.payload,
|
|
91
192
|
parameters,
|
|
193
|
+
timeoutMs,
|
|
92
194
|
loc: locFromNode(source, member)
|
|
93
195
|
};
|
|
94
196
|
}
|
|
@@ -97,7 +199,7 @@ function parseServiceMember(source, member) {
|
|
|
97
199
|
throw new errors_1.VerifyError("Service property must declare type.", locFromNode(source, member));
|
|
98
200
|
if (member.questionToken)
|
|
99
201
|
throw new errors_1.VerifyError("Optional service properties are not allowed.", locFromNode(source, member));
|
|
100
|
-
const parsed =
|
|
202
|
+
const parsed = parseMemberKindWithConfig(member.type);
|
|
101
203
|
if (!parsed)
|
|
102
204
|
throw new errors_1.VerifyError(`Unsupported service property type '${member.type.getText()}'.`, locFromNode(source, member));
|
|
103
205
|
if (parsed.kind !== "Input" && parsed.kind !== "Output") {
|
|
@@ -106,11 +208,13 @@ function parseServiceMember(source, member) {
|
|
|
106
208
|
if (!member.name || !typescript_1.default.isIdentifier(member.name)) {
|
|
107
209
|
throw new errors_1.VerifyError("Only identifier service property names are supported.", locFromNode(source, member));
|
|
108
210
|
}
|
|
211
|
+
const timeoutMs = resolveMemberTimeoutMs(source, serviceName, member.name.text, parsed.kind, parsed.configTypeNode, warnings, locFromNode(source, member));
|
|
109
212
|
return {
|
|
110
213
|
kind: parsed.kind,
|
|
111
214
|
name: member.name.text,
|
|
112
215
|
payloadTypeText: parsed.payload,
|
|
113
216
|
parameters: [],
|
|
217
|
+
timeoutMs,
|
|
114
218
|
loc: locFromNode(source, member)
|
|
115
219
|
};
|
|
116
220
|
}
|
|
@@ -207,7 +311,7 @@ function serviceBaseType(iface) {
|
|
|
207
311
|
}
|
|
208
312
|
return null;
|
|
209
313
|
}
|
|
210
|
-
function
|
|
314
|
+
function parseSpecFileAst(specFilePath) {
|
|
211
315
|
if (!node_fs_1.default.existsSync(specFilePath))
|
|
212
316
|
throw new errors_1.VerifyError(`Spec file does not exist: ${specFilePath}`);
|
|
213
317
|
const text = node_fs_1.default.readFileSync(specFilePath, "utf8");
|
|
@@ -225,12 +329,13 @@ function parseSpecFile(specFilePath) {
|
|
|
225
329
|
throw new errors_1.VerifyError("Namespace body must be a block.", locFromNode(source, ns));
|
|
226
330
|
const services = [];
|
|
227
331
|
const namespaceTypeDecls = [];
|
|
332
|
+
const warnings = [];
|
|
228
333
|
let supportsDevelopmentModeTransport = false;
|
|
229
334
|
for (const stmt of ns.body.statements) {
|
|
230
335
|
if (typescript_1.default.isInterfaceDeclaration(stmt)) {
|
|
231
336
|
const baseType = serviceBaseType(stmt);
|
|
232
337
|
if (baseType !== null) {
|
|
233
|
-
const members = stmt.members.map((member) => parseServiceMember(source, member));
|
|
338
|
+
const members = stmt.members.map((member) => parseServiceMember(source, stmt.name.text, member, warnings));
|
|
234
339
|
if (baseType === "AngularHTTPBaseServerClass") {
|
|
235
340
|
supportsDevelopmentModeTransport = true;
|
|
236
341
|
}
|
|
@@ -254,6 +359,19 @@ function parseSpecFile(specFilePath) {
|
|
|
254
359
|
namespaceTypeDecls,
|
|
255
360
|
importedTypeDecls: importInfo.importedTypeDecls,
|
|
256
361
|
importedTypeSymbols: importInfo.importedTypeSymbols,
|
|
257
|
-
specImports: importInfo.specImports
|
|
362
|
+
specImports: importInfo.specImports,
|
|
363
|
+
warnings
|
|
258
364
|
};
|
|
259
365
|
}
|
|
366
|
+
function parseSpecFile(specFilePath) {
|
|
367
|
+
(0, program_1.createTscProgramContext)(specFilePath);
|
|
368
|
+
const parsed = parseSpecFileAst(specFilePath);
|
|
369
|
+
if ((0, debug_dump_1.isDebugEnabled)()) {
|
|
370
|
+
(0, debug_dump_1.writeDebugFile)(process.cwd(), "anqstmodel/parsed-before-typegraph.txt", `${(0, debug_dump_1.inspectText)(parsed)}\n`);
|
|
371
|
+
}
|
|
372
|
+
const normalized = (0, typegraph_1.applyResolvedTypeGraph)(parsed);
|
|
373
|
+
if ((0, debug_dump_1.isDebugEnabled)()) {
|
|
374
|
+
(0, debug_dump_1.writeDebugFile)(process.cwd(), "anqstmodel/parsed-after-typegraph.txt", `${(0, debug_dump_1.inspectText)(normalized)}\n`);
|
|
375
|
+
}
|
|
376
|
+
return normalized;
|
|
377
|
+
}
|
|
@@ -9,7 +9,7 @@ exports.getProgramDiagnostics = getProgramDiagnostics;
|
|
|
9
9
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
11
|
const typescript_1 = __importDefault(require("typescript"));
|
|
12
|
-
const errors_1 = require("
|
|
12
|
+
const errors_1 = require("./errors");
|
|
13
13
|
const debug_dump_1 = require("./debug-dump");
|
|
14
14
|
const contextBySpecPath = new Map();
|
|
15
15
|
function diagnosticText(diagnostic) {
|