@valkyrjaio/sindri 26.5.0 → 26.6.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valkyrjaio/sindri",
3
- "version": "26.5.0",
3
+ "version": "26.6.0",
4
4
  "description": "The Valkyrja Sindri build tool.",
5
5
  "homepage": "https://www.valkyrja.io/sindri",
6
6
  "keywords": [
@@ -38,7 +38,7 @@
38
38
  "node": ">=22"
39
39
  },
40
40
  "dependencies": {
41
- "@valkyrjaio/valkyrja": "^26.4.1",
41
+ "@valkyrjaio/valkyrja": "^26.6.0",
42
42
  "ts-morph": "^28.0.0"
43
43
  },
44
44
  "scripts": {
@@ -612,6 +612,153 @@ export abstract class AstReader {
612
612
  return decorator.getArguments()[position];
613
613
  }
614
614
 
615
+ // -------------------------------------------------------------------------
616
+ // Object-literal decorator helpers — the shipped decorators accept a single
617
+ // options object (`@Route({ path, name, ... })`) rather than positional args.
618
+ // -------------------------------------------------------------------------
619
+
620
+ /**
621
+ * Return the decorator's first argument when it is an object literal, else undefined.
622
+ */
623
+ protected getDecoratorObjectArg(decorator: Decorator): ts.ObjectLiteralExpression | undefined {
624
+ const arg = this.getDecoratorArg(decorator, 0);
625
+
626
+ if (arg === undefined) {
627
+ return undefined;
628
+ }
629
+
630
+ const tsNode = arg.compilerNode;
631
+
632
+ return ts.isObjectLiteralExpression(tsNode) ? tsNode : undefined;
633
+ }
634
+
635
+ /**
636
+ * Return the value expression of the named property on an object literal, or
637
+ * undefined when the property is absent (or is not a plain property assignment).
638
+ */
639
+ protected getObjectProp(obj: ts.ObjectLiteralExpression, key: string): ts.Expression | undefined {
640
+ for (const prop of obj.properties) {
641
+ if (ts.isPropertyAssignment(prop) && this.propertyNameToString(prop.name) === key) {
642
+ return prop.initializer;
643
+ }
644
+ }
645
+
646
+ return undefined;
647
+ }
648
+
649
+ /**
650
+ * Resolve a property name node (identifier or string literal) to its text.
651
+ */
652
+ protected propertyNameToString(name: ts.PropertyName): string | undefined {
653
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
654
+ return name.text;
655
+ }
656
+
657
+ return undefined;
658
+ }
659
+
660
+ /**
661
+ * Extract a string-valued object property, falling back to defaultValue.
662
+ */
663
+ protected getObjectStringProp(
664
+ obj: ts.ObjectLiteralExpression,
665
+ key: string,
666
+ useMap: Record<string, string>,
667
+ currentFilePath: string,
668
+ currentClass: string,
669
+ defaultValue: string = '',
670
+ ): string {
671
+ const value = this.extractExprValue(this.getObjectProp(obj, key), useMap, currentFilePath, currentClass);
672
+
673
+ return typeof value === 'string' ? value : defaultValue;
674
+ }
675
+
676
+ /**
677
+ * Extract a boolean-valued object property, falling back to defaultValue.
678
+ */
679
+ protected getObjectBoolProp(
680
+ obj: ts.ObjectLiteralExpression,
681
+ key: string,
682
+ useMap: Record<string, string>,
683
+ currentFilePath: string,
684
+ currentClass: string,
685
+ defaultValue: boolean = false,
686
+ ): boolean {
687
+ const value = this.extractExprValue(this.getObjectProp(obj, key), useMap, currentFilePath, currentClass);
688
+
689
+ return typeof value === 'boolean' ? value : defaultValue;
690
+ }
691
+
692
+ /**
693
+ * Extract a class-list object property (an array literal of class references),
694
+ * returning an empty array when the property is absent or not an array.
695
+ */
696
+ protected getObjectClassListProp(
697
+ obj: ts.ObjectLiteralExpression,
698
+ key: string,
699
+ useMap: Record<string, string>,
700
+ currentFilePath: string,
701
+ currentClass: string,
702
+ ): string[] {
703
+ const node = this.getObjectProp(obj, key);
704
+
705
+ if (node === undefined || !ts.isArrayLiteralExpression(node)) {
706
+ return [];
707
+ }
708
+
709
+ return this.extractClassListFromArrayExpr(node, useMap, currentFilePath, currentClass);
710
+ }
711
+
712
+ /**
713
+ * Extract a string-list object property (an array literal of string values),
714
+ * returning an empty array when the property is absent or not an array.
715
+ */
716
+ protected getObjectStringListProp(
717
+ obj: ts.ObjectLiteralExpression,
718
+ key: string,
719
+ useMap: Record<string, string>,
720
+ currentFilePath: string,
721
+ currentClass: string,
722
+ ): string[] {
723
+ const node = this.getObjectProp(obj, key);
724
+
725
+ if (node === undefined || !ts.isArrayLiteralExpression(node)) {
726
+ return [];
727
+ }
728
+
729
+ const values: string[] = [];
730
+
731
+ for (const element of node.elements) {
732
+ const value = this.extractExprValue(element, useMap, currentFilePath, currentClass);
733
+
734
+ if (typeof value === 'string') {
735
+ values.push(value);
736
+ }
737
+ }
738
+
739
+ return values;
740
+ }
741
+
742
+ /**
743
+ * Extract a handler-reference object property (`[Class, 'method']`), returning
744
+ * undefined when the property is absent or not a two-element handler array.
745
+ */
746
+ protected getObjectHandlerProp(
747
+ obj: ts.ObjectLiteralExpression,
748
+ key: string,
749
+ useMap: Record<string, string>,
750
+ currentFilePath: string,
751
+ currentClass: string,
752
+ ): HandlerData | undefined {
753
+ const node = this.getObjectProp(obj, key);
754
+
755
+ if (node === undefined || !ts.isArrayLiteralExpression(node)) {
756
+ return undefined;
757
+ }
758
+
759
+ return this.extractHandlerFromTsArray(node, useMap, currentFilePath, currentClass);
760
+ }
761
+
615
762
  /**
616
763
  * Convert a simple expression node to a scalar value or HandlerData.
617
764
  */
@@ -692,7 +839,12 @@ export abstract class AstReader {
692
839
  return undefined;
693
840
  }
694
841
 
695
- const classValue = this.extractExprValue(array.elements[0], useMap, currentFilePath, currentClass);
842
+ const classValue = this.extractExprValue(
843
+ this.unwrapClassThunk(array.elements[0]),
844
+ useMap,
845
+ currentFilePath,
846
+ currentClass,
847
+ );
696
848
  const methodValue = this.extractExprValue(array.elements[1], useMap, currentFilePath, currentClass);
697
849
 
698
850
  if (typeof classValue !== 'string' || classValue === '') {
@@ -706,6 +858,35 @@ export abstract class AstReader {
706
858
  return new HandlerData(classValue, methodValue);
707
859
  }
708
860
 
861
+ /**
862
+ * Unwrap a class **thunk** to the class expression it returns —
863
+ * `() => HomeController` becomes `HomeController`.
864
+ *
865
+ * Every class reference in a decorator argument is authored as a thunk —
866
+ * `[() => SomeClass, 'method']` for a handler, `@Middleware(() => SomeClass)`
867
+ * for middleware — rather than as a bare class name. A TC39 Stage-3
868
+ * decorator runs while the class binding is still in its temporal dead zone,
869
+ * so naming the class directly throws at class-definition time on a circular
870
+ * or self-referential import. The thunk is purely a runtime deferral: the
871
+ * emitted cache is unchanged (`HttpRouteProvider.versionHandler`), so the
872
+ * reader simply looks through it. A bare class expression is returned
873
+ * untouched, and a block-bodied arrow (`() => { … }`) is left alone so it
874
+ * falls through to the usual "not a class reference" handling.
875
+ */
876
+ protected unwrapClassThunk(node: Node | ts.Node | undefined): Node | ts.Node | undefined {
877
+ if (node === undefined) {
878
+ return node;
879
+ }
880
+
881
+ const tsNode = Node.isNode(node) ? node.compilerNode : node;
882
+
883
+ if (!ts.isArrowFunction(tsNode) || tsNode.parameters.length !== 0) {
884
+ return node;
885
+ }
886
+
887
+ return ts.isBlock(tsNode.body) ? node : tsNode.body;
888
+ }
889
+
709
890
  protected extractClassListFromArrayExpr(
710
891
  array: ts.ArrayLiteralExpression,
711
892
  useMap: Record<string, string>,
@@ -715,7 +896,9 @@ export abstract class AstReader {
715
896
  const classes: string[] = [];
716
897
 
717
898
  for (const element of array.elements) {
718
- const value = this.extractExprValue(element, useMap, currentFilePath, currentClass);
899
+ // A class list inside a decorator argument holds thunked references
900
+ // (`middleware: [() => AuthMiddleware]`), so look through the thunk.
901
+ const value = this.extractExprValue(this.unwrapClassThunk(element), useMap, currentFilePath, currentClass);
719
902
 
720
903
  if (typeof value === 'string' && value !== '') {
721
904
  classes.push(value);
@@ -18,6 +18,7 @@ import type { Decorator, MethodDeclaration } from 'ts-morph';
18
18
 
19
19
  import type { CliRouteAttributeReaderContract } from './Contract/CliRouteAttributeReaderContract.ts';
20
20
  import type { CliRouteParameterReaderContract } from './Contract/CliRouteParameterReaderContract.ts';
21
+ import type { HandlerData } from './Data/HandlerData.ts';
21
22
 
22
23
  /**
23
24
  * Scans a CLI controller class file for @Route and related sub-decorators and
@@ -40,6 +41,7 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
40
41
 
41
42
  const { classDecl, useMap, currentClass } = context;
42
43
  const routes: Record<string, ts.Expression> = {};
44
+ const routeData: Record<string, CliRouteData> = {};
43
45
 
44
46
  for (const method of classDecl.getMethods()) {
45
47
  for (const decorator of this.findDecoratorsOnNode(method, 'Route', useMap, filePath)) {
@@ -47,11 +49,14 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
47
49
 
48
50
  if (data !== null) {
49
51
  routes[data.name] = this.buildRouteExpr(data);
52
+ routeData[data.name] = data;
50
53
  }
51
54
  }
52
55
  }
53
56
 
54
- return new CliRouteAttributeResult(routes);
57
+ const importMap = this.buildImportMap(routeData, useMap, filePath, currentClass);
58
+
59
+ return new CliRouteAttributeResult(routes, importMap);
55
60
  }
56
61
 
57
62
  protected getRouteHandlerDecoratorName(): string {
@@ -65,8 +70,14 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
65
70
  currentFilePath: string,
66
71
  currentClass: string,
67
72
  ): CliRouteData | null {
68
- const name = this.extractStringArg(decorator, 0, useMap, currentFilePath, currentClass);
69
- const description = this.extractStringArg(decorator, 1, useMap, currentFilePath, currentClass);
73
+ const obj = this.getDecoratorObjectArg(decorator);
74
+
75
+ if (obj === undefined) {
76
+ return null;
77
+ }
78
+
79
+ const name = this.getObjectStringProp(obj, 'name', useMap, currentFilePath, currentClass);
80
+ const description = this.getObjectStringProp(obj, 'description', useMap, currentFilePath, currentClass);
70
81
 
71
82
  if (name === '' || description === '') {
72
83
  return null;
@@ -79,17 +90,14 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
79
90
  useMap,
80
91
  currentFilePath,
81
92
  currentClass,
82
- this.extractClassListArgFromDecorator(decorator, 4, useMap, currentFilePath, currentClass),
83
- this.extractClassListArgFromDecorator(decorator, 5, useMap, currentFilePath, currentClass),
84
- this.extractClassListArgFromDecorator(decorator, 6, useMap, currentFilePath, currentClass),
85
- this.extractClassListArgFromDecorator(decorator, 7, useMap, currentFilePath, currentClass),
93
+ this.getObjectClassListProp(obj, 'middleware', useMap, currentFilePath, currentClass),
86
94
  );
87
95
 
88
96
  return new CliRouteData(
89
97
  updatedName,
90
98
  description,
91
- this.updateHandler(method, useMap, currentFilePath, currentClass),
92
- null,
99
+ this.resolveHandler(obj, method, useMap, currentFilePath, currentClass),
100
+ this.getObjectHandlerProp(obj, 'helpText', useMap, currentFilePath, currentClass) ?? null,
93
101
  routeMatchedMiddleware,
94
102
  routeDispatchedMiddleware,
95
103
  throwableCaughtMiddleware,
@@ -99,6 +107,22 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
99
107
  );
100
108
  }
101
109
 
110
+ protected resolveHandler(
111
+ obj: ts.ObjectLiteralExpression,
112
+ method: MethodDeclaration,
113
+ useMap: Record<string, string>,
114
+ currentFilePath: string,
115
+ currentClass: string,
116
+ ): HandlerData {
117
+ const fromObj = this.getObjectHandlerProp(obj, 'handler', useMap, currentFilePath, currentClass);
118
+
119
+ if (fromObj !== undefined) {
120
+ return fromObj;
121
+ }
122
+
123
+ return this.updateHandler(method, useMap, currentFilePath, currentClass);
124
+ }
125
+
102
126
  protected updateName(
103
127
  name: string,
104
128
  method: MethodDeclaration,
@@ -127,14 +151,30 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
127
151
  useMap: Record<string, string>,
128
152
  currentFilePath: string,
129
153
  currentClass: string,
130
- routeMatchedMiddleware: string[],
131
- routeDispatchedMiddleware: string[],
132
- throwableCaughtMiddleware: string[],
133
- processExitingMiddleware: string[],
154
+ middleware: string[],
134
155
  ): [string[], string[], string[], string[]] {
156
+ let routeMatchedMiddleware: string[] = [];
157
+ let routeDispatchedMiddleware: string[] = [];
158
+ let throwableCaughtMiddleware: string[] = [];
159
+ let processExitingMiddleware: string[] = [];
160
+
161
+ for (const mwName of middleware) {
162
+ [routeMatchedMiddleware, routeDispatchedMiddleware, throwableCaughtMiddleware, processExitingMiddleware] =
163
+ this.classifyMiddleware(
164
+ mwName,
165
+ useMap,
166
+ currentFilePath,
167
+ routeMatchedMiddleware,
168
+ routeDispatchedMiddleware,
169
+ throwableCaughtMiddleware,
170
+ processExitingMiddleware,
171
+ );
172
+ }
173
+
135
174
  for (const decorator of this.findDecoratorsOnNode(method, 'Middleware', useMap, currentFilePath)) {
175
+ // `@Middleware` takes a thunked class reference (`() => AuthMiddleware`).
136
176
  const mwName = this.extractExprValue(
137
- this.getDecoratorArg(decorator, 0),
177
+ this.unwrapClassThunk(this.getDecoratorArg(decorator, 0)),
138
178
  useMap,
139
179
  currentFilePath,
140
180
  currentClass,
@@ -216,4 +256,50 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
216
256
  this.buildClassArrayExpr(data.processExitingMiddleware),
217
257
  ];
218
258
  }
259
+
260
+ /**
261
+ * Build the class-name → absolute-file-path map for every handler and
262
+ * help-text class the generated data cache references, so the generator can
263
+ * emit the matching import statements. (Middleware are emitted as string
264
+ * literals and therefore need no import.)
265
+ */
266
+ protected buildImportMap(
267
+ routeData: Record<string, CliRouteData>,
268
+ useMap: Record<string, string>,
269
+ currentFilePath: string,
270
+ currentClass: string,
271
+ ): Record<string, string> {
272
+ const importMap: Record<string, string> = {};
273
+
274
+ for (const data of Object.values(routeData)) {
275
+ if (data.handler !== null) {
276
+ this.addClassImport(importMap, data.handler.class, useMap, currentFilePath, currentClass);
277
+ }
278
+
279
+ if (data.helpText !== null) {
280
+ this.addClassImport(importMap, data.helpText.class, useMap, currentFilePath, currentClass);
281
+ }
282
+ }
283
+
284
+ return importMap;
285
+ }
286
+
287
+ protected addClassImport(
288
+ importMap: Record<string, string>,
289
+ className: string,
290
+ useMap: Record<string, string>,
291
+ currentFilePath: string,
292
+ currentClass: string,
293
+ ): void {
294
+ const shortName = className.slice(className.lastIndexOf('\\') + 1);
295
+
296
+ const filePath =
297
+ shortName === currentClass
298
+ ? currentFilePath
299
+ : this.resolveImportToFilePath(shortName, useMap, currentFilePath);
300
+
301
+ if (filePath !== '') {
302
+ importMap[shortName] = filePath;
303
+ }
304
+ }
219
305
  }
@@ -28,7 +28,10 @@ export class CliRouteParameterReader extends AstReader implements CliRouteParame
28
28
  buildParameterArgs(data: CliRouteData): ts.Expression[] {
29
29
  const args: ts.Expression[] = [];
30
30
 
31
- if (data.arguments.length > 0) {
31
+ // The `arguments_` constructor parameter precedes `options`, so an empty
32
+ // arguments array must still be emitted whenever options are present, to
33
+ // keep `options` in its correct positional slot.
34
+ if (data.arguments.length > 0 || data.options.length > 0) {
32
35
  args.push(this.buildArgumentListExpr(data.arguments));
33
36
  }
34
37
 
@@ -83,8 +86,14 @@ export class CliRouteParameterReader extends AstReader implements CliRouteParame
83
86
  currentFilePath: string,
84
87
  currentClass: string,
85
88
  ): CliArgumentParameterData | null {
86
- const name = this.extractStringArg(decorator, 0, useMap, currentFilePath, currentClass);
87
- const description = this.extractStringArg(decorator, 1, useMap, currentFilePath, currentClass);
89
+ const obj = this.getDecoratorObjectArg(decorator);
90
+
91
+ if (obj === undefined) {
92
+ return null;
93
+ }
94
+
95
+ const name = this.getObjectStringProp(obj, 'name', useMap, currentFilePath, currentClass);
96
+ const description = this.getObjectStringProp(obj, 'description', useMap, currentFilePath, currentClass);
88
97
 
89
98
  if (name === '' || description === '') {
90
99
  return null;
@@ -93,18 +102,18 @@ export class CliRouteParameterReader extends AstReader implements CliRouteParame
93
102
  return new CliArgumentParameterData(
94
103
  name,
95
104
  description,
96
- this.extractStringArg(decorator, 2, useMap, currentFilePath, currentClass) || null,
97
- this.extractStringArg(
98
- decorator,
99
- 3,
105
+ this.getObjectStringProp(obj, 'cast', useMap, currentFilePath, currentClass) || null,
106
+ this.getObjectStringProp(
107
+ obj,
108
+ 'mode',
100
109
  useMap,
101
110
  currentFilePath,
102
111
  currentClass,
103
112
  'Valkyrja\\Cli\\Routing\\Enum\\ArgumentMode::OPTIONAL',
104
113
  ),
105
- this.extractStringArg(
106
- decorator,
107
- 4,
114
+ this.getObjectStringProp(
115
+ obj,
116
+ 'valueMode',
108
117
  useMap,
109
118
  currentFilePath,
110
119
  currentClass,
@@ -119,8 +128,14 @@ export class CliRouteParameterReader extends AstReader implements CliRouteParame
119
128
  currentFilePath: string,
120
129
  currentClass: string,
121
130
  ): CliOptionParameterData | null {
122
- const name = this.extractStringArg(decorator, 0, useMap, currentFilePath, currentClass);
123
- const description = this.extractStringArg(decorator, 1, useMap, currentFilePath, currentClass);
131
+ const obj = this.getDecoratorObjectArg(decorator);
132
+
133
+ if (obj === undefined) {
134
+ return null;
135
+ }
136
+
137
+ const name = this.getObjectStringProp(obj, 'name', useMap, currentFilePath, currentClass);
138
+ const description = this.getObjectStringProp(obj, 'description', useMap, currentFilePath, currentClass);
124
139
 
125
140
  if (name === '' || description === '') {
126
141
  return null;
@@ -129,22 +144,22 @@ export class CliRouteParameterReader extends AstReader implements CliRouteParame
129
144
  return new CliOptionParameterData(
130
145
  name,
131
146
  description,
132
- this.extractStringArg(decorator, 2, useMap, currentFilePath, currentClass),
133
- this.extractStringArg(decorator, 3, useMap, currentFilePath, currentClass) || null,
134
- this.extractStringArg(decorator, 4, useMap, currentFilePath, currentClass),
135
- this.extractStringListArgFromDecorator(decorator, 5, useMap, currentFilePath, currentClass),
136
- this.extractStringListArgFromDecorator(decorator, 6, useMap, currentFilePath, currentClass),
137
- this.extractStringArg(
138
- decorator,
139
- 7,
147
+ this.getObjectStringProp(obj, 'valueDisplayName', useMap, currentFilePath, currentClass),
148
+ this.getObjectStringProp(obj, 'cast', useMap, currentFilePath, currentClass) || null,
149
+ this.getObjectStringProp(obj, 'defaultValue', useMap, currentFilePath, currentClass),
150
+ this.getObjectStringListProp(obj, 'shortNames', useMap, currentFilePath, currentClass),
151
+ this.getObjectStringListProp(obj, 'validValues', useMap, currentFilePath, currentClass),
152
+ this.getObjectStringProp(
153
+ obj,
154
+ 'mode',
140
155
  useMap,
141
156
  currentFilePath,
142
157
  currentClass,
143
158
  'Valkyrja\\Cli\\Routing\\Enum\\OptionMode::OPTIONAL',
144
159
  ),
145
- this.extractStringArg(
146
- decorator,
147
- 8,
160
+ this.getObjectStringProp(
161
+ obj,
162
+ 'valueMode',
148
163
  useMap,
149
164
  currentFilePath,
150
165
  currentClass,
@@ -196,6 +211,10 @@ export class CliRouteParameterReader extends AstReader implements CliRouteParame
196
211
  args.push(ts.factory.createArrayLiteralExpression([]));
197
212
  }
198
213
 
214
+ // The framework `OptionParameter` constructor carries a runtime-populated
215
+ // `options` array between `validValues` and `mode`; emit an empty array so
216
+ // `mode`/`valueMode` land in their correct positional slots.
217
+ args.push(ts.factory.createArrayLiteralExpression([]));
199
218
  args.push(this.buildEnumCaseExpr(data.mode));
200
219
  args.push(this.buildEnumCaseExpr(data.valueMode));
201
220
 
@@ -14,8 +14,8 @@ import type { MethodDeclaration } from 'ts-morph';
14
14
  import type { HttpRouteData } from '../Data/HttpRouteData.ts';
15
15
 
16
16
  export interface HttpRouteMiddlewareReaderContract {
17
- extractInlineRequestMethods(
18
- decoratorArgs: ts.NodeArray<ts.Expression> | ts.Expression[],
17
+ extractObjectRequestMethods(
18
+ obj: ts.ObjectLiteralExpression,
19
19
  useMap: Record<string, string>,
20
20
  namespace: string,
21
21
  currentClass: string,
@@ -34,11 +34,7 @@ export interface HttpRouteMiddlewareReaderContract {
34
34
  useMap: Record<string, string>,
35
35
  namespace: string,
36
36
  currentClass: string,
37
- routeMatchedMiddleware: string[],
38
- routeDispatchedMiddleware: string[],
39
- throwableCaughtMiddleware: string[],
40
- sendingResponseMiddleware: string[],
41
- responseSentMiddleware: string[],
37
+ middleware: string[],
42
38
  ): [string[], string[], string[], string[], string[]];
43
39
 
44
40
  updateRequestStruct(
@@ -9,14 +9,11 @@
9
9
 
10
10
  import type { ts } from 'ts-morph';
11
11
 
12
- import type { MethodDeclaration } from 'ts-morph';
13
-
14
12
  import type { HttpParameterData } from '../Data/HttpParameterData.ts';
15
13
 
16
14
  export interface HttpRouteParameterReaderContract {
17
15
  updateParameters(
18
- decoratorArgs: ts.NodeArray<ts.Expression> | ts.Expression[],
19
- method: MethodDeclaration,
16
+ obj: ts.ObjectLiteralExpression,
20
17
  useMap: Record<string, string>,
21
18
  namespace: string,
22
19
  currentClass: string,
@@ -14,4 +14,6 @@ export interface HttpParameterDataContract {
14
14
  readonly cast: string | null;
15
15
  readonly isOptional: boolean;
16
16
  readonly shouldCapture: boolean;
17
+ /** The default value for the parameter, or null when none was declared. */
18
+ readonly defaultValue: string | number | boolean | null;
17
19
  }
@@ -16,5 +16,6 @@ export class HttpParameterData implements HttpParameterDataContract {
16
16
  readonly cast: string | null = null,
17
17
  readonly isOptional: boolean = false,
18
18
  readonly shouldCapture: boolean = true,
19
+ readonly defaultValue: string | number | boolean | null = null,
19
20
  ) {}
20
21
  }
@@ -10,5 +10,9 @@
10
10
  import type { ts } from 'ts-morph';
11
11
 
12
12
  export class CliRouteAttributeResult {
13
- constructor(readonly routes: Record<string, ts.Expression> = {}) {}
13
+ constructor(
14
+ readonly routes: Record<string, ts.Expression> = {},
15
+ /** Class short name → absolute file path for handler/help-text imports. */
16
+ readonly importMap: Record<string, string> = {},
17
+ ) {}
14
18
  }
@@ -15,5 +15,7 @@ export class HttpRouteAttributeResult {
15
15
  constructor(
16
16
  readonly routes: Record<string, ts.Expression> = {},
17
17
  readonly routeData: Record<string, HttpRouteData> = {},
18
+ /** Class short name → absolute file path for handler/middleware imports. */
19
+ readonly importMap: Record<string, string> = {},
18
20
  ) {}
19
21
  }