@nestbridge/webpack 2.0.1 → 2.0.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 +94 -0
- package/package.json +1 -1
- package/build/nestbridge.webpack.d.ts +0 -19
- package/build/nestbridge.webpack.js +0 -1116
- package/build/nestbridge.webpack.js.map +0 -1
|
@@ -1,1116 +0,0 @@
|
|
|
1
|
-
import { SyntaxKind, isArrayLiteralExpression, isArrayTypeNode, isArrowFunction, isBlock, isCallExpression, isClassDeclaration, isDecorator, isExportDeclaration, isFunctionExpression, isIdentifier, isImportDeclaration, isMethodDeclaration, isNamedExports, isNamedImports, isObjectLiteralExpression, isPropertyAssignment, isPropertyDeclaration, isReturnStatement, isStringLiteralLikeNode, isTypeReferenceNode, isUnionTypeNode } from "typescript/unstable/ast";
|
|
2
|
-
import { API } from "typescript/unstable/sync";
|
|
3
|
-
import { existsSync, globSync, mkdirSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { dirname, extname, join, posix, relative, resolve } from "node:path";
|
|
5
|
-
import { createUnplugin } from "unplugin";
|
|
6
|
-
//#region ../unplugin/build/nestbridge.unplugin.js
|
|
7
|
-
var getDecorators = (node) => {
|
|
8
|
-
const decorators = node.modifiers?.filter(isDecorator);
|
|
9
|
-
return decorators === void 0 || decorators.length === 0 ? void 0 : decorators;
|
|
10
|
-
};
|
|
11
|
-
var findDecorator = (node, decoratorName) => {
|
|
12
|
-
const decorators = getDecorators(node);
|
|
13
|
-
if (decorators === void 0) return;
|
|
14
|
-
return decorators.find((decorator) => decoratorNameOf(decorator) === decoratorName);
|
|
15
|
-
};
|
|
16
|
-
var findAnyDecorator = (node, decoratorNames) => {
|
|
17
|
-
const decorators = getDecorators(node);
|
|
18
|
-
if (decorators === void 0) return;
|
|
19
|
-
return decorators.find((decorator) => decoratorNames.includes(decoratorNameOf(decorator) ?? ""));
|
|
20
|
-
};
|
|
21
|
-
var decoratorNameOf = (decorator) => {
|
|
22
|
-
const expression = decorator.expression;
|
|
23
|
-
if (isCallExpression(expression) && isIdentifier(expression.expression)) return expression.expression.text;
|
|
24
|
-
if (isIdentifier(expression)) return expression.text;
|
|
25
|
-
};
|
|
26
|
-
var readStaticStringArgument = (decorator) => {
|
|
27
|
-
const expression = decorator.expression;
|
|
28
|
-
if (!isCallExpression(expression)) return {
|
|
29
|
-
ok: true,
|
|
30
|
-
value: void 0
|
|
31
|
-
};
|
|
32
|
-
const [firstArgument] = expression.arguments;
|
|
33
|
-
if (firstArgument === void 0) return {
|
|
34
|
-
ok: true,
|
|
35
|
-
value: void 0
|
|
36
|
-
};
|
|
37
|
-
if (isStringLiteralLikeNode(firstArgument)) return {
|
|
38
|
-
ok: true,
|
|
39
|
-
value: firstArgument.text
|
|
40
|
-
};
|
|
41
|
-
return {
|
|
42
|
-
ok: false,
|
|
43
|
-
argumentText: firstArgument.getText()
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
var HTTP_METHOD_DECORATOR_NAMES = {
|
|
47
|
-
Get: "GET",
|
|
48
|
-
Post: "POST",
|
|
49
|
-
Put: "PUT",
|
|
50
|
-
Patch: "PATCH",
|
|
51
|
-
Delete: "DELETE"
|
|
52
|
-
};
|
|
53
|
-
var locationOf = (sourceFile, node) => {
|
|
54
|
-
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
55
|
-
return {
|
|
56
|
-
line: line + 1,
|
|
57
|
-
column: character + 1
|
|
58
|
-
};
|
|
59
|
-
};
|
|
60
|
-
var SUPPORTED_PARAMETER_DECORATORS = [
|
|
61
|
-
"Param",
|
|
62
|
-
"Query",
|
|
63
|
-
"Body",
|
|
64
|
-
"Headers"
|
|
65
|
-
];
|
|
66
|
-
var KNOWN_UNSUPPORTED_PARAMETER_DECORATORS = [
|
|
67
|
-
"Req",
|
|
68
|
-
"Res",
|
|
69
|
-
"Request",
|
|
70
|
-
"Response"
|
|
71
|
-
];
|
|
72
|
-
var extractParameterDefinition = (parameter, index, context) => {
|
|
73
|
-
const decorator = findAnyDecorator(parameter, [...SUPPORTED_PARAMETER_DECORATORS, ...KNOWN_UNSUPPORTED_PARAMETER_DECORATORS]);
|
|
74
|
-
const location = locationOf(context.sourceFile, parameter);
|
|
75
|
-
if (decorator === void 0) {
|
|
76
|
-
context.diagnostics.push({
|
|
77
|
-
code: "unsupported-parameter",
|
|
78
|
-
title: "Unsupported parameter.",
|
|
79
|
-
controllerName: context.controllerName,
|
|
80
|
-
memberName: context.methodName,
|
|
81
|
-
detail: `Parameter "${parameter.name.getText()}" must be decorated with one of @Param, @Query, @Body, or @Headers to be transported through a NestBridge HTTP client.`,
|
|
82
|
-
filePath: context.filePath,
|
|
83
|
-
line: location.line,
|
|
84
|
-
column: location.column
|
|
85
|
-
});
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
const decoratorName = decoratorNameOf(decorator);
|
|
89
|
-
if (decoratorName !== void 0 && KNOWN_UNSUPPORTED_PARAMETER_DECORATORS.includes(decoratorName)) {
|
|
90
|
-
context.diagnostics.push({
|
|
91
|
-
code: "unsupported-parameter",
|
|
92
|
-
title: "Unsupported parameter.",
|
|
93
|
-
controllerName: context.controllerName,
|
|
94
|
-
memberName: context.methodName,
|
|
95
|
-
detail: `@${decoratorName}() cannot be transported through a NestBridge HTTP client.`,
|
|
96
|
-
filePath: context.filePath,
|
|
97
|
-
line: location.line,
|
|
98
|
-
column: location.column
|
|
99
|
-
});
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (!isIdentifier(parameter.name)) {
|
|
103
|
-
context.diagnostics.push({
|
|
104
|
-
code: "unsupported-parameter",
|
|
105
|
-
title: "Unsupported parameter.",
|
|
106
|
-
controllerName: context.controllerName,
|
|
107
|
-
memberName: context.methodName,
|
|
108
|
-
detail: "Destructured parameters cannot be transported through a NestBridge HTTP client.",
|
|
109
|
-
filePath: context.filePath,
|
|
110
|
-
line: location.line,
|
|
111
|
-
column: location.column
|
|
112
|
-
});
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
const argument = readStaticStringArgument(decorator);
|
|
116
|
-
if (!argument.ok) {
|
|
117
|
-
context.diagnostics.push({
|
|
118
|
-
code: "unsupported-parameter",
|
|
119
|
-
title: "Unsupported parameter.",
|
|
120
|
-
controllerName: context.controllerName,
|
|
121
|
-
memberName: context.methodName,
|
|
122
|
-
detail: `@${decoratorName}() arguments must be statically analyzable string literals.`,
|
|
123
|
-
found: `@${decoratorName}(${argument.argumentText})`,
|
|
124
|
-
filePath: context.filePath,
|
|
125
|
-
line: location.line,
|
|
126
|
-
column: location.column
|
|
127
|
-
});
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
const base = {
|
|
131
|
-
index,
|
|
132
|
-
parameterName: parameter.name.text
|
|
133
|
-
};
|
|
134
|
-
if (decoratorName === "Param") {
|
|
135
|
-
if (argument.value === void 0) {
|
|
136
|
-
context.diagnostics.push({
|
|
137
|
-
code: "unsupported-parameter",
|
|
138
|
-
title: "Unsupported parameter.",
|
|
139
|
-
controllerName: context.controllerName,
|
|
140
|
-
memberName: context.methodName,
|
|
141
|
-
detail: "A whole @Param() is not supported; provide a route parameter name such as @Param('id').",
|
|
142
|
-
filePath: context.filePath,
|
|
143
|
-
line: location.line,
|
|
144
|
-
column: location.column
|
|
145
|
-
});
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
return {
|
|
149
|
-
...base,
|
|
150
|
-
kind: "param",
|
|
151
|
-
name: argument.value
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
if (decoratorName === "Headers") {
|
|
155
|
-
if (argument.value === void 0) {
|
|
156
|
-
context.diagnostics.push({
|
|
157
|
-
code: "unsupported-parameter",
|
|
158
|
-
title: "Unsupported parameter.",
|
|
159
|
-
controllerName: context.controllerName,
|
|
160
|
-
memberName: context.methodName,
|
|
161
|
-
detail: "A whole @Headers() is not supported; provide a header name such as @Headers('x-example').",
|
|
162
|
-
filePath: context.filePath,
|
|
163
|
-
line: location.line,
|
|
164
|
-
column: location.column
|
|
165
|
-
});
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
return {
|
|
169
|
-
...base,
|
|
170
|
-
kind: "header",
|
|
171
|
-
name: argument.value
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
if (decoratorName === "Query") return {
|
|
175
|
-
...base,
|
|
176
|
-
kind: "query",
|
|
177
|
-
name: argument.value
|
|
178
|
-
};
|
|
179
|
-
return {
|
|
180
|
-
...base,
|
|
181
|
-
kind: "body",
|
|
182
|
-
name: argument.value
|
|
183
|
-
};
|
|
184
|
-
};
|
|
185
|
-
var PRIMITIVE_SCALAR_TYPES = {
|
|
186
|
-
string: "String",
|
|
187
|
-
number: "Float",
|
|
188
|
-
boolean: "Boolean"
|
|
189
|
-
};
|
|
190
|
-
var unwrapPromise = (typeNode) => {
|
|
191
|
-
if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName) && typeNode.typeName.text === "Promise" && typeNode.typeArguments?.[0] !== void 0) return unwrapPromise(typeNode.typeArguments[0]);
|
|
192
|
-
return typeNode;
|
|
193
|
-
};
|
|
194
|
-
var unwrapNullable = (typeNode) => {
|
|
195
|
-
if (!isUnionTypeNode(typeNode)) return {
|
|
196
|
-
inner: typeNode,
|
|
197
|
-
isNullable: false
|
|
198
|
-
};
|
|
199
|
-
const nonNullMembers = typeNode.types.filter((member) => member.kind !== SyntaxKind.NullKeyword && member.kind !== SyntaxKind.UndefinedKeyword);
|
|
200
|
-
const [onlyMember] = nonNullMembers;
|
|
201
|
-
if (nonNullMembers.length !== 1 || onlyMember === void 0) return {
|
|
202
|
-
inner: typeNode,
|
|
203
|
-
isNullable: nonNullMembers.length !== typeNode.types.length
|
|
204
|
-
};
|
|
205
|
-
return {
|
|
206
|
-
inner: onlyMember,
|
|
207
|
-
isNullable: true
|
|
208
|
-
};
|
|
209
|
-
};
|
|
210
|
-
var unwrapArrayElement = (typeNode) => {
|
|
211
|
-
if (isArrayTypeNode(typeNode)) return typeNode.elementType;
|
|
212
|
-
if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName) && typeNode.typeName.text === "Array" && typeNode.typeArguments?.[0] !== void 0) return typeNode.typeArguments[0];
|
|
213
|
-
};
|
|
214
|
-
var identifierTextOf = (typeNode) => {
|
|
215
|
-
if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName)) return typeNode.typeName.text;
|
|
216
|
-
switch (typeNode.kind) {
|
|
217
|
-
case SyntaxKind.StringKeyword: return "string";
|
|
218
|
-
case SyntaxKind.NumberKeyword: return "number";
|
|
219
|
-
case SyntaxKind.BooleanKeyword: return "boolean";
|
|
220
|
-
default: return;
|
|
221
|
-
}
|
|
222
|
-
};
|
|
223
|
-
var unwrapTypeNode = (typeNode) => {
|
|
224
|
-
const { inner: withoutNullable, isNullable } = unwrapNullable(unwrapPromise(typeNode));
|
|
225
|
-
const arrayElement = unwrapArrayElement(withoutNullable);
|
|
226
|
-
const identifierText = identifierTextOf(arrayElement === void 0 ? withoutNullable : unwrapNullable(arrayElement).inner);
|
|
227
|
-
if (identifierText === void 0) return;
|
|
228
|
-
return {
|
|
229
|
-
identifierText,
|
|
230
|
-
isArray: arrayElement !== void 0,
|
|
231
|
-
isNullable
|
|
232
|
-
};
|
|
233
|
-
};
|
|
234
|
-
var graphqlScalarNameFor = (identifierText) => PRIMITIVE_SCALAR_TYPES[identifierText] ?? identifierText;
|
|
235
|
-
var graphqlTypeStringFor = (unwrapped, explicitTypeName) => {
|
|
236
|
-
const baseName = explicitTypeName ?? graphqlScalarNameFor(unwrapped.identifierText);
|
|
237
|
-
const wrapped = unwrapped.isArray ? `[${baseName}!]` : baseName;
|
|
238
|
-
return unwrapped.isNullable ? wrapped : `${wrapped}!`;
|
|
239
|
-
};
|
|
240
|
-
var OBSERVABLE_TYPE_NAME = "Observable";
|
|
241
|
-
var STREAMABLE_FILE_TYPE_NAME = "StreamableFile";
|
|
242
|
-
var readResponseKind = (returnType) => {
|
|
243
|
-
if (returnType === void 0) return "json";
|
|
244
|
-
const unwrapped = unwrapPromise(returnType);
|
|
245
|
-
if (!isTypeReferenceNode(unwrapped) || !isIdentifier(unwrapped.typeName)) return "json";
|
|
246
|
-
if (unwrapped.typeName.text === OBSERVABLE_TYPE_NAME) return "observable";
|
|
247
|
-
if (unwrapped.typeName.text === STREAMABLE_FILE_TYPE_NAME) return "stream";
|
|
248
|
-
return "json";
|
|
249
|
-
};
|
|
250
|
-
var trimSlashes = (segment) => segment.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
251
|
-
var joinRoutePath = (controllerPath, methodPath) => {
|
|
252
|
-
return `/${[trimSlashes(controllerPath), trimSlashes(methodPath ?? "")].filter((segment) => segment.length > 0).join("/")}`;
|
|
253
|
-
};
|
|
254
|
-
var extractMethodDefinition = (method, context) => {
|
|
255
|
-
const decorator = findAnyDecorator(method, Object.keys(HTTP_METHOD_DECORATOR_NAMES));
|
|
256
|
-
if (decorator === void 0) return;
|
|
257
|
-
const methodName = method.name.getText();
|
|
258
|
-
const decoratorName = decoratorNameOf(decorator);
|
|
259
|
-
const location = locationOf(context.sourceFile, method);
|
|
260
|
-
if (decoratorName === void 0) return;
|
|
261
|
-
const httpMethod = HTTP_METHOD_DECORATOR_NAMES[decoratorName];
|
|
262
|
-
if (httpMethod === void 0) return;
|
|
263
|
-
const routeArgument = readStaticStringArgument(decorator);
|
|
264
|
-
if (!routeArgument.ok) {
|
|
265
|
-
context.diagnostics.push({
|
|
266
|
-
code: "unsupported-route",
|
|
267
|
-
title: "Unsupported route.",
|
|
268
|
-
controllerName: context.controllerName,
|
|
269
|
-
memberName: methodName,
|
|
270
|
-
detail: "Route paths must be statically analyzable.",
|
|
271
|
-
found: `@${decoratorName}(${routeArgument.argumentText})`,
|
|
272
|
-
filePath: context.filePath,
|
|
273
|
-
line: location.line,
|
|
274
|
-
column: location.column
|
|
275
|
-
});
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
const parameters = method.parameters.map((parameter, index) => extractParameterDefinition(parameter, index, {
|
|
279
|
-
sourceFile: context.sourceFile,
|
|
280
|
-
filePath: context.filePath,
|
|
281
|
-
controllerName: context.controllerName,
|
|
282
|
-
methodName,
|
|
283
|
-
diagnostics: context.diagnostics
|
|
284
|
-
}));
|
|
285
|
-
if (parameters.some((parameter) => parameter === void 0)) return;
|
|
286
|
-
return {
|
|
287
|
-
name: methodName,
|
|
288
|
-
httpMethod,
|
|
289
|
-
path: joinRoutePath(context.controllerPath, routeArgument.value),
|
|
290
|
-
parameters: parameters.filter((parameter) => parameter !== void 0),
|
|
291
|
-
responseKind: readResponseKind(method.type),
|
|
292
|
-
line: location.line,
|
|
293
|
-
column: location.column
|
|
294
|
-
};
|
|
295
|
-
};
|
|
296
|
-
var extractControllerDefinition = (classDeclaration, sourceFile, filePath, diagnostics) => {
|
|
297
|
-
const controllerName = classDeclaration.name?.text ?? "(anonymous controller)";
|
|
298
|
-
const decorator = findDecorator(classDeclaration, "Controller");
|
|
299
|
-
if (decorator === void 0) return;
|
|
300
|
-
const location = locationOf(sourceFile, classDeclaration);
|
|
301
|
-
const argument = readStaticStringArgument(decorator);
|
|
302
|
-
if (!argument.ok) {
|
|
303
|
-
diagnostics.push({
|
|
304
|
-
code: "unsupported-route",
|
|
305
|
-
title: "Unsupported route.",
|
|
306
|
-
controllerName,
|
|
307
|
-
memberName: "",
|
|
308
|
-
detail: "Controller paths must be statically analyzable.",
|
|
309
|
-
found: `@Controller(${argument.argumentText})`,
|
|
310
|
-
filePath,
|
|
311
|
-
line: location.line,
|
|
312
|
-
column: location.column
|
|
313
|
-
});
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
const methods = classDeclaration.members.filter((member) => isMethodDeclaration(member)).map((method) => extractMethodDefinition(method, {
|
|
317
|
-
sourceFile,
|
|
318
|
-
filePath,
|
|
319
|
-
controllerName,
|
|
320
|
-
controllerPath: argument.value ?? "",
|
|
321
|
-
diagnostics
|
|
322
|
-
})).filter((method) => method !== void 0);
|
|
323
|
-
return {
|
|
324
|
-
name: controllerName,
|
|
325
|
-
path: argument.value ?? "",
|
|
326
|
-
sourceFile: filePath,
|
|
327
|
-
methods
|
|
328
|
-
};
|
|
329
|
-
};
|
|
330
|
-
var findControllerClasses = (sourceFile) => {
|
|
331
|
-
return sourceFile.statements.filter((statement) => isClassDeclaration(statement) && findDecorator(statement, "Controller") !== void 0);
|
|
332
|
-
};
|
|
333
|
-
var sharedApi;
|
|
334
|
-
var parseSourceFile = (filePath) => {
|
|
335
|
-
sharedApi ??= new API();
|
|
336
|
-
const sourceFile = sharedApi.updateSnapshot({ openFiles: [filePath] }).getDefaultProjectForFile(filePath)?.program.getSourceFile(filePath);
|
|
337
|
-
if (sourceFile === void 0) throw new Error(`Unable to parse source file: ${filePath}`);
|
|
338
|
-
return sourceFile;
|
|
339
|
-
};
|
|
340
|
-
var analyzeControllers = (filePaths) => {
|
|
341
|
-
const diagnostics = [];
|
|
342
|
-
const controllers = [];
|
|
343
|
-
for (const filePath of filePaths) {
|
|
344
|
-
const sourceFile = parseSourceFile(filePath);
|
|
345
|
-
for (const classDeclaration of findControllerClasses(sourceFile)) {
|
|
346
|
-
const controller = extractControllerDefinition(classDeclaration, sourceFile, filePath, diagnostics);
|
|
347
|
-
if (controller !== void 0) controllers.push(controller);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
return {
|
|
351
|
-
controllers,
|
|
352
|
-
diagnostics
|
|
353
|
-
};
|
|
354
|
-
};
|
|
355
|
-
var GRAPHQL_OPERATION_DECORATOR_NAMES = {
|
|
356
|
-
Query: "query",
|
|
357
|
-
Mutation: "mutation"
|
|
358
|
-
};
|
|
359
|
-
var readTypeThunkExpression = (thunk) => {
|
|
360
|
-
if (!isBlock(thunk.body)) return thunk.body;
|
|
361
|
-
return thunk.body.statements.find(isReturnStatement)?.expression;
|
|
362
|
-
};
|
|
363
|
-
var readArgsTypeOption = (decorator) => {
|
|
364
|
-
if (!isCallExpression(decorator.expression)) return;
|
|
365
|
-
const optionsArgument = decorator.expression.arguments.find(isObjectLiteralExpression);
|
|
366
|
-
if (optionsArgument === void 0) return;
|
|
367
|
-
const typeProperty = optionsArgument.properties.find((property) => isPropertyAssignment(property) && isIdentifier(property.name) && property.name.text === "type");
|
|
368
|
-
if (typeProperty === void 0) return;
|
|
369
|
-
const thunk = typeProperty.initializer;
|
|
370
|
-
if (!isArrowFunction(thunk) && !isFunctionExpression(thunk)) return;
|
|
371
|
-
const returnedExpression = readTypeThunkExpression(thunk);
|
|
372
|
-
if (returnedExpression === void 0) return;
|
|
373
|
-
if (isArrayLiteralExpression(returnedExpression)) {
|
|
374
|
-
const element = returnedExpression.elements[0];
|
|
375
|
-
return element !== void 0 && isIdentifier(element) ? {
|
|
376
|
-
typeName: element.text,
|
|
377
|
-
isArray: true
|
|
378
|
-
} : void 0;
|
|
379
|
-
}
|
|
380
|
-
return isIdentifier(returnedExpression) ? {
|
|
381
|
-
typeName: returnedExpression.text,
|
|
382
|
-
isArray: false
|
|
383
|
-
} : void 0;
|
|
384
|
-
};
|
|
385
|
-
var extractResolverArgumentDefinition = (parameter, index, context) => {
|
|
386
|
-
const decorator = findAnyDecorator(parameter, ["Args"]);
|
|
387
|
-
const location = locationOf(context.sourceFile, parameter);
|
|
388
|
-
if (decorator === void 0) {
|
|
389
|
-
context.diagnostics.push({
|
|
390
|
-
code: "unsupported-argument",
|
|
391
|
-
title: "Unsupported argument.",
|
|
392
|
-
controllerName: context.resolverName,
|
|
393
|
-
memberName: context.methodName,
|
|
394
|
-
detail: `Parameter "${parameter.name.getText()}" must be decorated with @Args to be transported through a NestBridge GraphQL client.`,
|
|
395
|
-
filePath: context.filePath,
|
|
396
|
-
line: location.line,
|
|
397
|
-
column: location.column
|
|
398
|
-
});
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
if (!isIdentifier(parameter.name)) {
|
|
402
|
-
context.diagnostics.push({
|
|
403
|
-
code: "unsupported-argument",
|
|
404
|
-
title: "Unsupported argument.",
|
|
405
|
-
controllerName: context.resolverName,
|
|
406
|
-
memberName: context.methodName,
|
|
407
|
-
detail: "Destructured parameters cannot be transported through a NestBridge GraphQL client.",
|
|
408
|
-
filePath: context.filePath,
|
|
409
|
-
line: location.line,
|
|
410
|
-
column: location.column
|
|
411
|
-
});
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
const nameArgument = readStaticStringArgument(decorator);
|
|
415
|
-
if (!nameArgument.ok) {
|
|
416
|
-
context.diagnostics.push({
|
|
417
|
-
code: "unsupported-argument",
|
|
418
|
-
title: "Unsupported argument.",
|
|
419
|
-
controllerName: context.resolverName,
|
|
420
|
-
memberName: context.methodName,
|
|
421
|
-
detail: "@Args() name must be a statically analyzable string literal.",
|
|
422
|
-
found: `@Args(${nameArgument.argumentText})`,
|
|
423
|
-
filePath: context.filePath,
|
|
424
|
-
line: location.line,
|
|
425
|
-
column: location.column
|
|
426
|
-
});
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
if (nameArgument.value === void 0) {
|
|
430
|
-
context.diagnostics.push({
|
|
431
|
-
code: "unsupported-argument",
|
|
432
|
-
title: "Unsupported argument.",
|
|
433
|
-
controllerName: context.resolverName,
|
|
434
|
-
memberName: context.methodName,
|
|
435
|
-
detail: "A whole @Args() is not supported; provide an argument name such as @Args('id').",
|
|
436
|
-
filePath: context.filePath,
|
|
437
|
-
line: location.line,
|
|
438
|
-
column: location.column
|
|
439
|
-
});
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
if (parameter.type === void 0) {
|
|
443
|
-
context.diagnostics.push({
|
|
444
|
-
code: "unsupported-argument",
|
|
445
|
-
title: "Unsupported argument.",
|
|
446
|
-
controllerName: context.resolverName,
|
|
447
|
-
memberName: context.methodName,
|
|
448
|
-
detail: `@Args() parameter "${parameter.name.text}" needs an explicit type annotation.`,
|
|
449
|
-
filePath: context.filePath,
|
|
450
|
-
line: location.line,
|
|
451
|
-
column: location.column
|
|
452
|
-
});
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
const unwrapped = unwrapTypeNode(parameter.type);
|
|
456
|
-
if (unwrapped === void 0) {
|
|
457
|
-
context.diagnostics.push({
|
|
458
|
-
code: "unsupported-argument",
|
|
459
|
-
title: "Unsupported argument.",
|
|
460
|
-
controllerName: context.resolverName,
|
|
461
|
-
memberName: context.methodName,
|
|
462
|
-
detail: `@Args() parameter "${parameter.name.text}" has a type NestBridge cannot statically resolve to a GraphQL type.`,
|
|
463
|
-
found: parameter.type.getText(),
|
|
464
|
-
filePath: context.filePath,
|
|
465
|
-
line: location.line,
|
|
466
|
-
column: location.column
|
|
467
|
-
});
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
const explicitType = readArgsTypeOption(decorator);
|
|
471
|
-
return {
|
|
472
|
-
index,
|
|
473
|
-
parameterName: parameter.name.text,
|
|
474
|
-
name: nameArgument.value,
|
|
475
|
-
graphqlType: graphqlTypeStringFor(unwrapped, explicitType?.typeName)
|
|
476
|
-
};
|
|
477
|
-
};
|
|
478
|
-
var parsedFilesByPath = /* @__PURE__ */ new Map();
|
|
479
|
-
var parseFile = (filePath) => {
|
|
480
|
-
const cached = parsedFilesByPath.get(filePath);
|
|
481
|
-
if (cached !== void 0) return cached;
|
|
482
|
-
const sourceFile = parseSourceFile(filePath);
|
|
483
|
-
parsedFilesByPath.set(filePath, sourceFile);
|
|
484
|
-
return sourceFile;
|
|
485
|
-
};
|
|
486
|
-
var resolveModuleFile = (fromFilePath, moduleSpecifierText) => {
|
|
487
|
-
if (!moduleSpecifierText.startsWith(".")) return;
|
|
488
|
-
const base = resolve(dirname(fromFilePath), moduleSpecifierText);
|
|
489
|
-
return [
|
|
490
|
-
`${base}.ts`,
|
|
491
|
-
`${base}.tsx`,
|
|
492
|
-
resolve(base, "index.ts")
|
|
493
|
-
].find((candidate) => existsSync(candidate));
|
|
494
|
-
};
|
|
495
|
-
var findExportedDeclaration = (sourceFile, name) => sourceFile.statements.find((statement) => isClassDeclaration(statement) && statement.name?.text === name);
|
|
496
|
-
var findImportSource = (sourceFile, localName) => {
|
|
497
|
-
for (const statement of sourceFile.statements) {
|
|
498
|
-
if (!isImportDeclaration(statement) || !isStringLiteralLikeNode(statement.moduleSpecifier)) continue;
|
|
499
|
-
const namedBindings = statement.importClause?.namedBindings;
|
|
500
|
-
if (namedBindings === void 0 || !isNamedImports(namedBindings)) continue;
|
|
501
|
-
const element = namedBindings.elements.find((candidate) => candidate.name.text === localName);
|
|
502
|
-
if (element !== void 0) return {
|
|
503
|
-
moduleSpecifierText: statement.moduleSpecifier.text,
|
|
504
|
-
importedName: element.propertyName?.text ?? element.name.text
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
};
|
|
508
|
-
var findReExportSource = (sourceFile, name) => {
|
|
509
|
-
for (const statement of sourceFile.statements) {
|
|
510
|
-
if (!isExportDeclaration(statement) || statement.moduleSpecifier === void 0 || !isStringLiteralLikeNode(statement.moduleSpecifier)) continue;
|
|
511
|
-
if (statement.exportClause === void 0) return statement.moduleSpecifier.text;
|
|
512
|
-
if (isNamedExports(statement.exportClause) && statement.exportClause.elements.some((element) => (element.propertyName?.text ?? element.name.text) === name)) return statement.moduleSpecifier.text;
|
|
513
|
-
}
|
|
514
|
-
};
|
|
515
|
-
var resolveClassDeclaration = (name, fromSourceFile, fromFilePath, visited = /* @__PURE__ */ new Set()) => {
|
|
516
|
-
if (visited.has(fromFilePath)) return;
|
|
517
|
-
visited.add(fromFilePath);
|
|
518
|
-
const direct = findExportedDeclaration(fromSourceFile, name);
|
|
519
|
-
if (direct !== void 0) return {
|
|
520
|
-
classDeclaration: direct,
|
|
521
|
-
sourceFile: fromSourceFile,
|
|
522
|
-
filePath: fromFilePath
|
|
523
|
-
};
|
|
524
|
-
const importSource = findImportSource(fromSourceFile, name);
|
|
525
|
-
if (importSource !== void 0) {
|
|
526
|
-
const resolvedPath = resolveModuleFile(fromFilePath, importSource.moduleSpecifierText);
|
|
527
|
-
if (resolvedPath !== void 0) {
|
|
528
|
-
const nextSourceFile = parseFile(resolvedPath);
|
|
529
|
-
const result = resolveClassDeclaration(importSource.importedName, nextSourceFile, resolvedPath, visited);
|
|
530
|
-
if (result !== void 0) return result;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
const reExportModuleSpecifierText = findReExportSource(fromSourceFile, name);
|
|
534
|
-
if (reExportModuleSpecifierText !== void 0) {
|
|
535
|
-
const resolvedPath = resolveModuleFile(fromFilePath, reExportModuleSpecifierText);
|
|
536
|
-
if (resolvedPath !== void 0) return resolveClassDeclaration(name, parseFile(resolvedPath), resolvedPath, visited);
|
|
537
|
-
}
|
|
538
|
-
};
|
|
539
|
-
var PRIMITIVE_SCALAR_NAMES$1 = /* @__PURE__ */ new Set([
|
|
540
|
-
"string",
|
|
541
|
-
"number",
|
|
542
|
-
"boolean"
|
|
543
|
-
]);
|
|
544
|
-
var extractSelectionFields = (classDeclaration, sourceFile, filePath, context) => {
|
|
545
|
-
const typeName = classDeclaration.name?.text ?? "(anonymous type)";
|
|
546
|
-
const location = locationOf(sourceFile, classDeclaration);
|
|
547
|
-
if (context.visitedTypeNames.has(typeName)) {
|
|
548
|
-
context.diagnostics.push({
|
|
549
|
-
code: "circular-type",
|
|
550
|
-
title: "Circular type.",
|
|
551
|
-
controllerName: context.resolverName,
|
|
552
|
-
memberName: context.methodName,
|
|
553
|
-
detail: `Type "${typeName}" references itself, directly or indirectly. NestBridge cannot build a finite GraphQL selection set for it.`,
|
|
554
|
-
filePath,
|
|
555
|
-
line: location.line,
|
|
556
|
-
column: location.column
|
|
557
|
-
});
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
const nextVisited = new Set(context.visitedTypeNames).add(typeName);
|
|
561
|
-
const properties = classDeclaration.members.filter((member) => isPropertyDeclaration(member) && findDecorator(member, "Field") !== void 0);
|
|
562
|
-
const fields = [];
|
|
563
|
-
for (const property of properties) {
|
|
564
|
-
if (!isIdentifier(property.name)) continue;
|
|
565
|
-
const fieldName = property.name.text;
|
|
566
|
-
const fieldLocation = locationOf(sourceFile, property);
|
|
567
|
-
if (property.type === void 0) {
|
|
568
|
-
context.diagnostics.push({
|
|
569
|
-
code: "unsupported-return-type",
|
|
570
|
-
title: "Unsupported field type.",
|
|
571
|
-
controllerName: context.resolverName,
|
|
572
|
-
memberName: context.methodName,
|
|
573
|
-
detail: `Field "${fieldName}" on "${typeName}" needs an explicit type annotation.`,
|
|
574
|
-
filePath,
|
|
575
|
-
line: fieldLocation.line,
|
|
576
|
-
column: fieldLocation.column
|
|
577
|
-
});
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
const unwrapped = unwrapTypeNode(property.type);
|
|
581
|
-
if (unwrapped === void 0) {
|
|
582
|
-
context.diagnostics.push({
|
|
583
|
-
code: "unsupported-return-type",
|
|
584
|
-
title: "Unsupported field type.",
|
|
585
|
-
controllerName: context.resolverName,
|
|
586
|
-
memberName: context.methodName,
|
|
587
|
-
detail: `Field "${fieldName}" on "${typeName}" has a type NestBridge cannot statically resolve.`,
|
|
588
|
-
found: property.type.getText(),
|
|
589
|
-
filePath,
|
|
590
|
-
line: fieldLocation.line,
|
|
591
|
-
column: fieldLocation.column
|
|
592
|
-
});
|
|
593
|
-
return;
|
|
594
|
-
}
|
|
595
|
-
if (PRIMITIVE_SCALAR_NAMES$1.has(unwrapped.identifierText)) {
|
|
596
|
-
fields.push({ name: fieldName });
|
|
597
|
-
continue;
|
|
598
|
-
}
|
|
599
|
-
const resolved = resolveClassDeclaration(unwrapped.identifierText, sourceFile, filePath);
|
|
600
|
-
if (resolved === void 0) {
|
|
601
|
-
fields.push({ name: fieldName });
|
|
602
|
-
continue;
|
|
603
|
-
}
|
|
604
|
-
if (findDecorator(resolved.classDeclaration, "ObjectType") === void 0) {
|
|
605
|
-
context.diagnostics.push({
|
|
606
|
-
code: "unsupported-return-type",
|
|
607
|
-
title: "Unsupported field type.",
|
|
608
|
-
controllerName: context.resolverName,
|
|
609
|
-
memberName: context.methodName,
|
|
610
|
-
detail: `Field "${fieldName}" on "${typeName}" resolves to class "${unwrapped.identifierText}", which is not decorated with @ObjectType(). NestBridge cannot build a GraphQL selection set for it.`,
|
|
611
|
-
filePath,
|
|
612
|
-
line: fieldLocation.line,
|
|
613
|
-
column: fieldLocation.column
|
|
614
|
-
});
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
|
-
const children = extractSelectionFields(resolved.classDeclaration, resolved.sourceFile, resolved.filePath, {
|
|
618
|
-
...context,
|
|
619
|
-
visitedTypeNames: nextVisited
|
|
620
|
-
});
|
|
621
|
-
if (children === void 0) return;
|
|
622
|
-
fields.push({
|
|
623
|
-
name: fieldName,
|
|
624
|
-
children
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
return fields;
|
|
628
|
-
};
|
|
629
|
-
var readNameFromOptions = (optionsArgument) => {
|
|
630
|
-
const nameProperty = optionsArgument.properties.find((property) => isPropertyAssignment(property) && isIdentifier(property.name) && property.name.text === "name");
|
|
631
|
-
if (nameProperty === void 0 || !isStringLiteralLikeNode(nameProperty.initializer)) return;
|
|
632
|
-
return nameProperty.initializer.text;
|
|
633
|
-
};
|
|
634
|
-
var readGraphqlOperationName = (decorator) => {
|
|
635
|
-
const expression = decorator.expression;
|
|
636
|
-
if (!isCallExpression(expression)) return {
|
|
637
|
-
ok: true,
|
|
638
|
-
value: void 0
|
|
639
|
-
};
|
|
640
|
-
let value;
|
|
641
|
-
for (const argument of expression.arguments) {
|
|
642
|
-
if (isArrowFunction(argument) || isFunctionExpression(argument)) continue;
|
|
643
|
-
if (isStringLiteralLikeNode(argument)) {
|
|
644
|
-
value = argument.text;
|
|
645
|
-
continue;
|
|
646
|
-
}
|
|
647
|
-
if (isObjectLiteralExpression(argument)) {
|
|
648
|
-
value = readNameFromOptions(argument) ?? value;
|
|
649
|
-
continue;
|
|
650
|
-
}
|
|
651
|
-
return {
|
|
652
|
-
ok: false,
|
|
653
|
-
argumentText: argument.getText()
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
return {
|
|
657
|
-
ok: true,
|
|
658
|
-
value
|
|
659
|
-
};
|
|
660
|
-
};
|
|
661
|
-
var readGraphqlReturnTypeOption = (decorator) => {
|
|
662
|
-
if (!isCallExpression(decorator.expression)) return;
|
|
663
|
-
const thunk = decorator.expression.arguments.find((argument) => isArrowFunction(argument) || isFunctionExpression(argument));
|
|
664
|
-
if (thunk === void 0) return;
|
|
665
|
-
const returnedExpression = readTypeThunkExpression(thunk);
|
|
666
|
-
if (returnedExpression === void 0) return;
|
|
667
|
-
if (isArrayLiteralExpression(returnedExpression)) {
|
|
668
|
-
const element = returnedExpression.elements[0];
|
|
669
|
-
return element !== void 0 && isIdentifier(element) ? element.text : void 0;
|
|
670
|
-
}
|
|
671
|
-
return isIdentifier(returnedExpression) ? returnedExpression.text : void 0;
|
|
672
|
-
};
|
|
673
|
-
var PRIMITIVE_SCALAR_NAMES = /* @__PURE__ */ new Set([
|
|
674
|
-
"string",
|
|
675
|
-
"number",
|
|
676
|
-
"boolean"
|
|
677
|
-
]);
|
|
678
|
-
var extractResolverMethodDefinition = (method, context) => {
|
|
679
|
-
const decorator = findAnyDecorator(method, Object.keys(GRAPHQL_OPERATION_DECORATOR_NAMES));
|
|
680
|
-
if (decorator === void 0) return;
|
|
681
|
-
const methodName = method.name.getText();
|
|
682
|
-
const decoratorName = decoratorNameOf(decorator);
|
|
683
|
-
const location = locationOf(context.sourceFile, method);
|
|
684
|
-
if (decoratorName === void 0) return;
|
|
685
|
-
const operationKind = GRAPHQL_OPERATION_DECORATOR_NAMES[decoratorName];
|
|
686
|
-
if (operationKind === void 0) return;
|
|
687
|
-
const nameArgument = readGraphqlOperationName(decorator);
|
|
688
|
-
if (!nameArgument.ok) {
|
|
689
|
-
context.diagnostics.push({
|
|
690
|
-
code: "unsupported-route",
|
|
691
|
-
title: "Unsupported operation name.",
|
|
692
|
-
controllerName: context.resolverName,
|
|
693
|
-
memberName: methodName,
|
|
694
|
-
detail: "GraphQL operation names must be statically analyzable string literals.",
|
|
695
|
-
found: `@${decoratorName}(${nameArgument.argumentText})`,
|
|
696
|
-
filePath: context.filePath,
|
|
697
|
-
line: location.line,
|
|
698
|
-
column: location.column
|
|
699
|
-
});
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
|
-
const decoratorReturnType = readGraphqlReturnTypeOption(decorator);
|
|
703
|
-
const identifierText = decoratorReturnType ?? (method.type === void 0 ? void 0 : unwrapTypeNode(method.type)?.identifierText);
|
|
704
|
-
if (identifierText === void 0) {
|
|
705
|
-
context.diagnostics.push({
|
|
706
|
-
code: "unsupported-return-type",
|
|
707
|
-
title: "Unsupported return type.",
|
|
708
|
-
controllerName: context.resolverName,
|
|
709
|
-
memberName: methodName,
|
|
710
|
-
detail: "GraphQL resolver methods need a type thunk on @Query()/@Mutation() (e.g. \"() => UserType\") or an explicit return type annotation so NestBridge can build a selection set.",
|
|
711
|
-
filePath: context.filePath,
|
|
712
|
-
line: location.line,
|
|
713
|
-
column: location.column
|
|
714
|
-
});
|
|
715
|
-
return;
|
|
716
|
-
}
|
|
717
|
-
let selection = [];
|
|
718
|
-
if (!PRIMITIVE_SCALAR_NAMES.has(identifierText.toLowerCase())) {
|
|
719
|
-
const resolved = resolveClassDeclaration(identifierText, context.sourceFile, context.filePath);
|
|
720
|
-
if (resolved === void 0 && decoratorReturnType === void 0) {
|
|
721
|
-
context.diagnostics.push({
|
|
722
|
-
code: "unsupported-return-type",
|
|
723
|
-
title: "Unsupported return type.",
|
|
724
|
-
controllerName: context.resolverName,
|
|
725
|
-
memberName: methodName,
|
|
726
|
-
detail: `NestBridge could not locate the declaration of type "${identifierText}".`,
|
|
727
|
-
filePath: context.filePath,
|
|
728
|
-
line: location.line,
|
|
729
|
-
column: location.column
|
|
730
|
-
});
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
if (resolved !== void 0) {
|
|
734
|
-
const fields = extractSelectionFields(resolved.classDeclaration, resolved.sourceFile, resolved.filePath, {
|
|
735
|
-
resolverName: context.resolverName,
|
|
736
|
-
methodName,
|
|
737
|
-
diagnostics: context.diagnostics,
|
|
738
|
-
visitedTypeNames: /* @__PURE__ */ new Set()
|
|
739
|
-
});
|
|
740
|
-
if (fields === void 0) return;
|
|
741
|
-
selection = fields;
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
const args = method.parameters.map((parameter, index) => extractResolverArgumentDefinition(parameter, index, {
|
|
745
|
-
sourceFile: context.sourceFile,
|
|
746
|
-
filePath: context.filePath,
|
|
747
|
-
resolverName: context.resolverName,
|
|
748
|
-
methodName,
|
|
749
|
-
diagnostics: context.diagnostics
|
|
750
|
-
}));
|
|
751
|
-
if (args.some((argument) => argument === void 0)) return;
|
|
752
|
-
return {
|
|
753
|
-
name: methodName,
|
|
754
|
-
operationKind,
|
|
755
|
-
operationName: nameArgument.value ?? methodName,
|
|
756
|
-
arguments: args.filter((argument) => argument !== void 0),
|
|
757
|
-
selection,
|
|
758
|
-
line: location.line,
|
|
759
|
-
column: location.column
|
|
760
|
-
};
|
|
761
|
-
};
|
|
762
|
-
var extractResolverDefinition = (classDeclaration, sourceFile, filePath, diagnostics) => {
|
|
763
|
-
const resolverName = classDeclaration.name?.text ?? "(anonymous resolver)";
|
|
764
|
-
return {
|
|
765
|
-
name: resolverName,
|
|
766
|
-
sourceFile: filePath,
|
|
767
|
-
methods: classDeclaration.members.filter((member) => isMethodDeclaration(member)).map((method) => extractResolverMethodDefinition(method, {
|
|
768
|
-
sourceFile,
|
|
769
|
-
filePath,
|
|
770
|
-
resolverName,
|
|
771
|
-
diagnostics
|
|
772
|
-
})).filter((method) => method !== void 0)
|
|
773
|
-
};
|
|
774
|
-
};
|
|
775
|
-
var findResolverClasses = (sourceFile) => {
|
|
776
|
-
return sourceFile.statements.filter((statement) => isClassDeclaration(statement) && findDecorator(statement, "Resolver") !== void 0);
|
|
777
|
-
};
|
|
778
|
-
var analyzeResolvers = (filePaths) => {
|
|
779
|
-
const diagnostics = [];
|
|
780
|
-
const resolvers = [];
|
|
781
|
-
for (const filePath of filePaths) {
|
|
782
|
-
const sourceFile = parseSourceFile(filePath);
|
|
783
|
-
for (const classDeclaration of findResolverClasses(sourceFile)) {
|
|
784
|
-
const resolver = extractResolverDefinition(classDeclaration, sourceFile, filePath, diagnostics);
|
|
785
|
-
if (resolver !== void 0) resolvers.push(resolver);
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
return {
|
|
789
|
-
resolvers,
|
|
790
|
-
diagnostics
|
|
791
|
-
};
|
|
792
|
-
};
|
|
793
|
-
var REMOTE_RESULT_HELPERS = {
|
|
794
|
-
json: "RemoteResult",
|
|
795
|
-
observable: "RemoteObservableResult",
|
|
796
|
-
stream: "RemoteStreamResult"
|
|
797
|
-
};
|
|
798
|
-
var toPosixPath = (filePath) => filePath.replace(/\\/g, "/");
|
|
799
|
-
var importSpecifierFor = (sourceFile, outputFilePath) => {
|
|
800
|
-
if (!sourceFile.startsWith(".") && !/^[A-Za-z]:[\\/]/.test(sourceFile) && !sourceFile.startsWith("/")) return sourceFile;
|
|
801
|
-
const relativePath = toPosixPath(relative(dirname(outputFilePath), sourceFile));
|
|
802
|
-
return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
|
|
803
|
-
};
|
|
804
|
-
var stripExtension = (filePath) => {
|
|
805
|
-
const extension = extname(filePath);
|
|
806
|
-
return extension.length > 0 ? filePath.slice(0, -extension.length) : filePath;
|
|
807
|
-
};
|
|
808
|
-
var generateControllerDeclaration = (controller, outputFilePath) => {
|
|
809
|
-
const sourceFileSpecifier = importSpecifierFor(controller.sourceFile, outputFilePath);
|
|
810
|
-
const importSpecifier = stripExtension(sourceFileSpecifier);
|
|
811
|
-
const serverAlias = `__Server${controller.name}`;
|
|
812
|
-
const classBody = [
|
|
813
|
-
" constructor();",
|
|
814
|
-
"",
|
|
815
|
-
...controller.methods.map((method) => {
|
|
816
|
-
const helperName = REMOTE_RESULT_HELPERS[method.responseKind ?? "json"];
|
|
817
|
-
return ` ${method.name}(...args: Parameters<${serverAlias}['${method.name}']>): ${helperName}<${serverAlias}['${method.name}']>;`;
|
|
818
|
-
})
|
|
819
|
-
].join("\n");
|
|
820
|
-
const usedHelperNames = new Set(controller.methods.map((method) => REMOTE_RESULT_HELPERS[method.responseKind ?? "json"]));
|
|
821
|
-
const helperImports = Array.from(usedHelperNames.size > 0 ? usedHelperNames : ["RemoteResult"]).sort();
|
|
822
|
-
return [
|
|
823
|
-
`import type { ${controller.name} as ${serverAlias} } from '${importSpecifier}';`,
|
|
824
|
-
`import type { ${helperImports.join(", ")} } from 'nestbridge';`,
|
|
825
|
-
"",
|
|
826
|
-
"/**",
|
|
827
|
-
" * Generated by NestBridge — client-facing methods only.",
|
|
828
|
-
` * @see {@link ${sourceFileSpecifier}} for the real controller implementation.`,
|
|
829
|
-
" */",
|
|
830
|
-
`export declare class ${controller.name} {`,
|
|
831
|
-
classBody,
|
|
832
|
-
"}",
|
|
833
|
-
""
|
|
834
|
-
].join("\n");
|
|
835
|
-
};
|
|
836
|
-
var formatDiagnostic = (diagnostic) => {
|
|
837
|
-
const subject = diagnostic.memberName.length > 0 ? `${diagnostic.controllerName}.${diagnostic.memberName}` : diagnostic.controllerName;
|
|
838
|
-
const lines = [
|
|
839
|
-
`[NestBridge] ${diagnostic.title}`,
|
|
840
|
-
"",
|
|
841
|
-
subject,
|
|
842
|
-
"",
|
|
843
|
-
diagnostic.detail
|
|
844
|
-
];
|
|
845
|
-
if (diagnostic.found !== void 0) lines.push("", "Found:", diagnostic.found);
|
|
846
|
-
if (diagnostic.expected !== void 0) lines.push("", "Expected:", diagnostic.expected);
|
|
847
|
-
lines.push("", `${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}`);
|
|
848
|
-
return lines.join("\n");
|
|
849
|
-
};
|
|
850
|
-
var generateConfigModule = (options) => {
|
|
851
|
-
if (options.baseURL === void 0) return "";
|
|
852
|
-
return [
|
|
853
|
-
"import { setNestBridgeBaseURL } from 'nestbridge';",
|
|
854
|
-
"",
|
|
855
|
-
`setNestBridgeBaseURL(${JSON.stringify(options.baseURL)});`,
|
|
856
|
-
""
|
|
857
|
-
].join("\n");
|
|
858
|
-
};
|
|
859
|
-
/**
|
|
860
|
-
* Must not contain a colon: webpack treats any `letter+:` prefix as a URL
|
|
861
|
-
* scheme and routes the request past its filesystem resolver entirely,
|
|
862
|
-
* bypassing the resolveId hook this virtual module relies on.
|
|
863
|
-
*/
|
|
864
|
-
var CONFIG_VIRTUAL_MODULE_ID = "virtual-nestbridge-config";
|
|
865
|
-
var RESOLVED_CONFIG_VIRTUAL_MODULE_ID = `\0${CONFIG_VIRTUAL_MODULE_ID}`;
|
|
866
|
-
var mergeNamedParametersExpression = (parameters) => {
|
|
867
|
-
if (parameters.length === 0) return;
|
|
868
|
-
const whole = parameters.find((parameter) => parameter.name === void 0);
|
|
869
|
-
const named = parameters.filter((parameter) => parameter.name !== void 0);
|
|
870
|
-
const namedEntries = named.map((parameter) => `${JSON.stringify(parameter.name)}: ${parameter.parameterName}`);
|
|
871
|
-
if (whole === void 0) return `{ ${namedEntries.join(", ")} }`;
|
|
872
|
-
if (named.length === 0) return whole.parameterName;
|
|
873
|
-
return `{ ...${whole.parameterName}, ${namedEntries.join(", ")} }`;
|
|
874
|
-
};
|
|
875
|
-
var generateBodyExpression = (parameters) => mergeNamedParametersExpression(parameters.filter((parameter) => parameter.kind === "body"));
|
|
876
|
-
var generateHeadersExpression = (parameters) => {
|
|
877
|
-
const headerParameters = parameters.filter((parameter) => parameter.kind === "header");
|
|
878
|
-
if (headerParameters.length === 0) return;
|
|
879
|
-
return `{ ${headerParameters.map((parameter) => `${JSON.stringify(parameter.name)}: ${parameter.parameterName}`).join(", ")} }`;
|
|
880
|
-
};
|
|
881
|
-
var generateParameterList = (parameters) => [...parameters].sort((a, b) => a.index - b.index).map((parameter) => parameter.parameterName).join(", ");
|
|
882
|
-
var PARAM_TOKEN_PATTERN = /:([A-Za-z0-9_]+)/;
|
|
883
|
-
var generatePathExpression = (method) => {
|
|
884
|
-
const paramParameters = method.parameters.filter((parameter) => parameter.kind === "param");
|
|
885
|
-
const parts = method.path.split(PARAM_TOKEN_PATTERN);
|
|
886
|
-
const expressionParts = [];
|
|
887
|
-
parts.forEach((part, index) => {
|
|
888
|
-
if (!(index % 2 === 1)) {
|
|
889
|
-
if (part.length > 0) expressionParts.push(JSON.stringify(part));
|
|
890
|
-
return;
|
|
891
|
-
}
|
|
892
|
-
const match = paramParameters.find((parameter) => parameter.name === part);
|
|
893
|
-
expressionParts.push(match !== void 0 ? `encodeURIComponent(${match.parameterName})` : JSON.stringify(`:${part}`));
|
|
894
|
-
});
|
|
895
|
-
return expressionParts.length > 0 ? expressionParts.join(" + ") : "''";
|
|
896
|
-
};
|
|
897
|
-
var generateQueryExpression = (parameters) => mergeNamedParametersExpression(parameters.filter((parameter) => parameter.kind === "query"));
|
|
898
|
-
var generateMethod$1 = (method) => {
|
|
899
|
-
const requestProperties = [`method: ${JSON.stringify(method.httpMethod)}`, `path: ${generatePathExpression(method)}`];
|
|
900
|
-
const queryExpression = generateQueryExpression(method.parameters);
|
|
901
|
-
if (queryExpression !== void 0) requestProperties.push(`query: ${queryExpression}`);
|
|
902
|
-
const bodyExpression = generateBodyExpression(method.parameters);
|
|
903
|
-
if (bodyExpression !== void 0) requestProperties.push(`body: ${bodyExpression}`);
|
|
904
|
-
const headersExpression = generateHeadersExpression(method.parameters);
|
|
905
|
-
if (headersExpression !== void 0) requestProperties.push(`headers: ${headersExpression}`);
|
|
906
|
-
if (method.responseKind === "stream") requestProperties.push(`responseType: "blob"`);
|
|
907
|
-
return [
|
|
908
|
-
` ${method.name}(${generateParameterList(method.parameters)}) {`,
|
|
909
|
-
" return request({",
|
|
910
|
-
...requestProperties.map((property) => ` ${property},`),
|
|
911
|
-
" });",
|
|
912
|
-
" }"
|
|
913
|
-
].join("\n");
|
|
914
|
-
};
|
|
915
|
-
var generateControllerModule = (controller) => {
|
|
916
|
-
const methods = controller.methods.map(generateMethod$1).join("\n\n");
|
|
917
|
-
return [
|
|
918
|
-
`import '${CONFIG_VIRTUAL_MODULE_ID}';`,
|
|
919
|
-
"import { request } from 'nestbridge';",
|
|
920
|
-
"",
|
|
921
|
-
`export class ${controller.name} {`,
|
|
922
|
-
" constructor() {}",
|
|
923
|
-
"",
|
|
924
|
-
methods,
|
|
925
|
-
"}",
|
|
926
|
-
""
|
|
927
|
-
].join("\n");
|
|
928
|
-
};
|
|
929
|
-
var capitalize = (value) => `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
|
|
930
|
-
var renderSelectionSet = (fields) => {
|
|
931
|
-
return `{ ${fields.map((field) => field.children === void 0 ? field.name : `${field.name} ${renderSelectionSet(field.children)}`).join(" ")} }`;
|
|
932
|
-
};
|
|
933
|
-
var generateGraphqlDocument = (method) => {
|
|
934
|
-
const variableDeclarations = method.arguments.map((argument) => `$${argument.name}: ${argument.graphqlType}`).join(", ");
|
|
935
|
-
const fieldArguments = method.arguments.map((argument) => `${argument.name}: $${argument.name}`).join(", ");
|
|
936
|
-
const variablesClause = variableDeclarations.length > 0 ? `(${variableDeclarations})` : "";
|
|
937
|
-
const fieldArgumentsClause = fieldArguments.length > 0 ? `(${fieldArguments})` : "";
|
|
938
|
-
const fieldSelection = method.selection.length > 0 ? ` ${renderSelectionSet(method.selection)}` : "";
|
|
939
|
-
return `${method.operationKind} ${capitalize(method.name)}${variablesClause} { ${method.operationName}${fieldArgumentsClause}${fieldSelection} }`;
|
|
940
|
-
};
|
|
941
|
-
var generateResolverArgumentList = (resolverArguments) => [...resolverArguments].sort((a, b) => a.index - b.index).map((argument) => argument.parameterName).join(", ");
|
|
942
|
-
var generateVariablesExpression = (resolverArguments) => {
|
|
943
|
-
if (resolverArguments.length === 0) return;
|
|
944
|
-
return `{ ${resolverArguments.map((argument) => `${JSON.stringify(argument.name)}: ${argument.parameterName}`).join(", ")} }`;
|
|
945
|
-
};
|
|
946
|
-
var generateMethod = (method) => {
|
|
947
|
-
const requestProperties = [`document: ${JSON.stringify(generateGraphqlDocument(method))}`];
|
|
948
|
-
const variablesExpression = generateVariablesExpression(method.arguments);
|
|
949
|
-
if (variablesExpression !== void 0) requestProperties.push(`variables: ${variablesExpression}`);
|
|
950
|
-
return [
|
|
951
|
-
` async ${method.name}(${generateResolverArgumentList(method.arguments)}) {`,
|
|
952
|
-
" const result = await graphqlRequest({",
|
|
953
|
-
...requestProperties.map((property) => ` ${property},`),
|
|
954
|
-
" });",
|
|
955
|
-
"",
|
|
956
|
-
` return result.${method.operationName};`,
|
|
957
|
-
" }"
|
|
958
|
-
].join("\n");
|
|
959
|
-
};
|
|
960
|
-
var generateResolverModule = (resolver) => {
|
|
961
|
-
const methods = resolver.methods.map(generateMethod).join("\n\n");
|
|
962
|
-
return [
|
|
963
|
-
`import '${CONFIG_VIRTUAL_MODULE_ID}';`,
|
|
964
|
-
"import { graphqlRequest } from 'nestbridge';",
|
|
965
|
-
"",
|
|
966
|
-
`export class ${resolver.name} {`,
|
|
967
|
-
" constructor() {}",
|
|
968
|
-
"",
|
|
969
|
-
methods,
|
|
970
|
-
"}",
|
|
971
|
-
""
|
|
972
|
-
].join("\n");
|
|
973
|
-
};
|
|
974
|
-
var stripLeadingParentSegments = (relativePath) => {
|
|
975
|
-
const segments = relativePath.split(/[\\/]/);
|
|
976
|
-
const firstOwnedIndex = segments.findIndex((segment) => segment !== "..");
|
|
977
|
-
return segments.slice(firstOwnedIndex === -1 ? segments.length : firstOwnedIndex).join("/");
|
|
978
|
-
};
|
|
979
|
-
var mirroredDeclarationPath = (root, outputDir, controllerFilePath) => {
|
|
980
|
-
const mirroredPath = stripLeadingParentSegments(relative(root, controllerFilePath)).replace(/\.ts$/, ".d.ts");
|
|
981
|
-
return join(root, outputDir, mirroredPath);
|
|
982
|
-
};
|
|
983
|
-
var writeControllerDeclarations = (controllers, root, outputDir) => {
|
|
984
|
-
for (const controller of controllers) {
|
|
985
|
-
const outputFilePath = mirroredDeclarationPath(root, outputDir, controller.sourceFile);
|
|
986
|
-
mkdirSync(dirname(outputFilePath), { recursive: true });
|
|
987
|
-
writeFileSync(outputFilePath, generateControllerDeclaration(controller, outputFilePath), "utf-8");
|
|
988
|
-
}
|
|
989
|
-
};
|
|
990
|
-
var writeResolverDeclarations = (resolvers, root, outputDir) => {
|
|
991
|
-
for (const resolver of resolvers) {
|
|
992
|
-
const outputFilePath = mirroredDeclarationPath(root, outputDir, resolver.sourceFile);
|
|
993
|
-
mkdirSync(dirname(outputFilePath), { recursive: true });
|
|
994
|
-
writeFileSync(outputFilePath, generateControllerDeclaration(resolver, outputFilePath), "utf-8");
|
|
995
|
-
}
|
|
996
|
-
};
|
|
997
|
-
var discoverFiles = (patterns, root) => {
|
|
998
|
-
const matches = globSync([...patterns], { cwd: root });
|
|
999
|
-
return [...new Set(matches.map((match) => resolve(root, match)))].sort();
|
|
1000
|
-
};
|
|
1001
|
-
var resolveNestBridgeOptions = (options) => ({
|
|
1002
|
-
controllers: Array.isArray(options.controllers) ? options.controllers : [options.controllers],
|
|
1003
|
-
resolvers: options.resolvers === void 0 ? [] : Array.isArray(options.resolvers) ? options.resolvers : [options.resolvers],
|
|
1004
|
-
baseURL: options.baseURL,
|
|
1005
|
-
debug: options.debug ?? false,
|
|
1006
|
-
outputDir: options.outputDir ?? ".nestbridge",
|
|
1007
|
-
root: options.root
|
|
1008
|
-
});
|
|
1009
|
-
var isWindows = process.platform === "win32";
|
|
1010
|
-
var normalizePath = (filePath) => posix.normalize(isWindows ? filePath.replaceAll("\\", "/") : filePath);
|
|
1011
|
-
var createControllerRegistry = (patterns, root) => {
|
|
1012
|
-
const controllersByFile = /* @__PURE__ */ new Map();
|
|
1013
|
-
const refresh = () => {
|
|
1014
|
-
controllersByFile.clear();
|
|
1015
|
-
const { controllers, diagnostics } = analyzeControllers(discoverFiles(patterns, root));
|
|
1016
|
-
for (const controller of controllers) controllersByFile.set(normalizePath(controller.sourceFile), controller);
|
|
1017
|
-
return {
|
|
1018
|
-
controllers,
|
|
1019
|
-
diagnostics
|
|
1020
|
-
};
|
|
1021
|
-
};
|
|
1022
|
-
return {
|
|
1023
|
-
refresh,
|
|
1024
|
-
get: (filePath) => controllersByFile.get(filePath),
|
|
1025
|
-
has: (filePath) => controllersByFile.has(filePath),
|
|
1026
|
-
all: () => [...controllersByFile.values()]
|
|
1027
|
-
};
|
|
1028
|
-
};
|
|
1029
|
-
var createResolverRegistry = (patterns, root) => {
|
|
1030
|
-
const resolversByFile = /* @__PURE__ */ new Map();
|
|
1031
|
-
const refresh = () => {
|
|
1032
|
-
resolversByFile.clear();
|
|
1033
|
-
const { resolvers, diagnostics } = analyzeResolvers(discoverFiles(patterns, root));
|
|
1034
|
-
for (const resolver of resolvers) resolversByFile.set(normalizePath(resolver.sourceFile), resolver);
|
|
1035
|
-
return {
|
|
1036
|
-
resolvers,
|
|
1037
|
-
diagnostics
|
|
1038
|
-
};
|
|
1039
|
-
};
|
|
1040
|
-
return {
|
|
1041
|
-
refresh,
|
|
1042
|
-
get: (filePath) => resolversByFile.get(filePath),
|
|
1043
|
-
has: (filePath) => resolversByFile.has(filePath),
|
|
1044
|
-
all: () => [...resolversByFile.values()]
|
|
1045
|
-
};
|
|
1046
|
-
};
|
|
1047
|
-
var reportDiagnostics = (diagnostics, watchMode) => {
|
|
1048
|
-
for (const diagnostic of diagnostics) {
|
|
1049
|
-
const message = formatDiagnostic(diagnostic);
|
|
1050
|
-
if (watchMode === false) throw new Error(message);
|
|
1051
|
-
console.warn(message);
|
|
1052
|
-
}
|
|
1053
|
-
};
|
|
1054
|
-
var nestBridgeUnplugin = createUnplugin((options, meta) => {
|
|
1055
|
-
const resolvedOptions = resolveNestBridgeOptions(options);
|
|
1056
|
-
let root = resolvedOptions.root ?? process.cwd();
|
|
1057
|
-
let controllerRegistry;
|
|
1058
|
-
let resolverRegistry;
|
|
1059
|
-
const log = (message) => {
|
|
1060
|
-
if (resolvedOptions.debug) console.log(`[nestbridge] ${message}`);
|
|
1061
|
-
};
|
|
1062
|
-
const runAnalysis = () => {
|
|
1063
|
-
const { controllers, diagnostics: controllerDiagnostics } = controllerRegistry.refresh();
|
|
1064
|
-
const { resolvers, diagnostics: resolverDiagnostics } = resolverRegistry.refresh();
|
|
1065
|
-
reportDiagnostics([...controllerDiagnostics, ...resolverDiagnostics], meta.watchMode);
|
|
1066
|
-
writeControllerDeclarations(controllers, root, resolvedOptions.outputDir);
|
|
1067
|
-
writeResolverDeclarations(resolvers, root, resolvedOptions.outputDir);
|
|
1068
|
-
log(`analyzed ${controllers.length} controller(s), ${resolvers.length} resolver(s), ${controllerDiagnostics.length + resolverDiagnostics.length} diagnostic(s)`);
|
|
1069
|
-
};
|
|
1070
|
-
return {
|
|
1071
|
-
name: "nestbridge",
|
|
1072
|
-
enforce: "pre",
|
|
1073
|
-
buildStart() {
|
|
1074
|
-
controllerRegistry = createControllerRegistry(resolvedOptions.controllers, root);
|
|
1075
|
-
resolverRegistry = createResolverRegistry(resolvedOptions.resolvers, root);
|
|
1076
|
-
runAnalysis();
|
|
1077
|
-
},
|
|
1078
|
-
resolveId(source) {
|
|
1079
|
-
if (source === "virtual-nestbridge-config") return RESOLVED_CONFIG_VIRTUAL_MODULE_ID;
|
|
1080
|
-
return null;
|
|
1081
|
-
},
|
|
1082
|
-
load(id) {
|
|
1083
|
-
if (id === RESOLVED_CONFIG_VIRTUAL_MODULE_ID) return generateConfigModule(resolvedOptions);
|
|
1084
|
-
return null;
|
|
1085
|
-
},
|
|
1086
|
-
transform(_code, id) {
|
|
1087
|
-
const absolutePath = normalizePath(id);
|
|
1088
|
-
const controller = controllerRegistry.get(absolutePath);
|
|
1089
|
-
if (controller !== void 0) return generateControllerModule(controller);
|
|
1090
|
-
const resolver = resolverRegistry.get(absolutePath);
|
|
1091
|
-
if (resolver !== void 0) return generateResolverModule(resolver);
|
|
1092
|
-
return null;
|
|
1093
|
-
},
|
|
1094
|
-
watchChange(id) {
|
|
1095
|
-
const changedFile = normalizePath(id);
|
|
1096
|
-
const wasTracked = controllerRegistry.has(changedFile) || resolverRegistry.has(changedFile);
|
|
1097
|
-
const isTrackedNow = discoverFiles(resolvedOptions.controllers, root).map(normalizePath).includes(changedFile) || discoverFiles(resolvedOptions.resolvers, root).map(normalizePath).includes(changedFile);
|
|
1098
|
-
if (!wasTracked && !isTrackedNow) return;
|
|
1099
|
-
runAnalysis();
|
|
1100
|
-
log(`nestbridge source changed: ${changedFile}`);
|
|
1101
|
-
},
|
|
1102
|
-
vite: { configResolved(config) {
|
|
1103
|
-
if (resolvedOptions.root === void 0) root = config.root;
|
|
1104
|
-
} }
|
|
1105
|
-
};
|
|
1106
|
-
});
|
|
1107
|
-
//#endregion
|
|
1108
|
-
//#region src/version.ts
|
|
1109
|
-
var VERSION = "2.0.1";
|
|
1110
|
-
//#endregion
|
|
1111
|
-
//#region src/main.ts
|
|
1112
|
-
var nestBridge = nestBridgeUnplugin.webpack;
|
|
1113
|
-
//#endregion
|
|
1114
|
-
export { VERSION, nestBridge };
|
|
1115
|
-
|
|
1116
|
-
//# sourceMappingURL=nestbridge.webpack.js.map
|