@nestbridge/webpack 2.0.2 → 2.0.4

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.
@@ -0,0 +1,1116 @@
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.4";
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