@valkyrjaio/sindri 26.5.1 → 26.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/LICENSE.md +1 -1
  2. package/dist/bin/sindri.cjs +574 -268
  3. package/package.json +2 -2
  4. package/src/Sindri/Ast/Abstract/AstReader.ts +187 -5
  5. package/src/Sindri/Ast/Abstract/RouteAttributeReader.ts +2 -3
  6. package/src/Sindri/Ast/CliRouteAttributeReader.ts +102 -17
  7. package/src/Sindri/Ast/CliRouteParameterReader.ts +44 -26
  8. package/src/Sindri/Ast/ComponentProviderReader.ts +2 -3
  9. package/src/Sindri/Ast/ConfigReader.ts +2 -3
  10. package/src/Sindri/Ast/ConfigSourceReader.ts +2 -3
  11. package/src/Sindri/Ast/Contract/CliRouteAttributeReaderContract.ts +2 -3
  12. package/src/Sindri/Ast/Contract/CliRouteParameterReaderContract.ts +2 -3
  13. package/src/Sindri/Ast/Contract/ComponentProviderReaderContract.ts +2 -3
  14. package/src/Sindri/Ast/Contract/ConfigReaderContract.ts +2 -3
  15. package/src/Sindri/Ast/Contract/ConfigSourceReaderContract.ts +2 -3
  16. package/src/Sindri/Ast/Contract/HttpRouteAttributeReaderContract.ts +2 -3
  17. package/src/Sindri/Ast/Contract/HttpRouteMiddlewareReaderContract.ts +5 -10
  18. package/src/Sindri/Ast/Contract/HttpRouteParameterReaderContract.ts +3 -7
  19. package/src/Sindri/Ast/Contract/ListenerAttributeReaderContract.ts +2 -3
  20. package/src/Sindri/Ast/Contract/ListenerProviderReaderContract.ts +2 -3
  21. package/src/Sindri/Ast/Contract/RouteProviderReaderContract.ts +2 -3
  22. package/src/Sindri/Ast/Contract/ServiceProviderReaderContract.ts +2 -3
  23. package/src/Sindri/Ast/Data/CliArgumentParameterData.ts +2 -3
  24. package/src/Sindri/Ast/Data/CliOptionParameterData.ts +2 -3
  25. package/src/Sindri/Ast/Data/CliRouteData.ts +2 -3
  26. package/src/Sindri/Ast/Data/ConfigData.ts +2 -3
  27. package/src/Sindri/Ast/Data/ConfigImport.ts +2 -3
  28. package/src/Sindri/Ast/Data/Contract/CliArgumentParameterDataContract.ts +2 -3
  29. package/src/Sindri/Ast/Data/Contract/CliOptionParameterDataContract.ts +2 -3
  30. package/src/Sindri/Ast/Data/Contract/CliRouteDataContract.ts +2 -3
  31. package/src/Sindri/Ast/Data/Contract/ConfigDataContract.ts +2 -3
  32. package/src/Sindri/Ast/Data/Contract/HandlerDataContract.ts +2 -3
  33. package/src/Sindri/Ast/Data/Contract/HttpParameterDataContract.ts +4 -3
  34. package/src/Sindri/Ast/Data/Contract/HttpRouteDataContract.ts +2 -3
  35. package/src/Sindri/Ast/Data/Contract/ListenerDataContract.ts +2 -3
  36. package/src/Sindri/Ast/Data/HandlerData.ts +2 -3
  37. package/src/Sindri/Ast/Data/HttpParameterData.ts +3 -3
  38. package/src/Sindri/Ast/Data/HttpRouteData.ts +2 -3
  39. package/src/Sindri/Ast/Data/ListenerData.ts +2 -3
  40. package/src/Sindri/Ast/Data/Result/CliRouteAttributeResult.ts +7 -4
  41. package/src/Sindri/Ast/Data/Result/ComponentProviderResult.ts +2 -3
  42. package/src/Sindri/Ast/Data/Result/ConfigResult.ts +2 -3
  43. package/src/Sindri/Ast/Data/Result/ConfigSourceResult.ts +2 -3
  44. package/src/Sindri/Ast/Data/Result/HttpRouteAttributeResult.ts +4 -3
  45. package/src/Sindri/Ast/Data/Result/ListenerAttributeResult.ts +2 -3
  46. package/src/Sindri/Ast/Data/Result/ListenerProviderResult.ts +2 -3
  47. package/src/Sindri/Ast/Data/Result/RouteProviderResult.ts +2 -3
  48. package/src/Sindri/Ast/Data/Result/ServiceProviderResult.ts +2 -3
  49. package/src/Sindri/Ast/HttpRouteAttributeReader.ts +180 -51
  50. package/src/Sindri/Ast/HttpRouteMiddlewareReader.ts +38 -18
  51. package/src/Sindri/Ast/HttpRouteParameterReader.ts +50 -109
  52. package/src/Sindri/Ast/ListenerAttributeReader.ts +2 -3
  53. package/src/Sindri/Ast/ListenerProviderReader.ts +2 -3
  54. package/src/Sindri/Ast/RouteProviderReader.ts +2 -3
  55. package/src/Sindri/Ast/ServiceProviderReader.ts +2 -3
  56. package/src/Sindri/Ast/Throwable/Contract/AstThrowable.ts +2 -3
  57. package/src/Sindri/Ast/Throwable/Exception/Abstract/AstInvalidArgumentException.ts +2 -3
  58. package/src/Sindri/Ast/Throwable/Exception/Abstract/AstRuntimeException.ts +2 -3
  59. package/src/Sindri/Ast/Throwable/Exception/AstFileReadException.ts +2 -3
  60. package/src/Sindri/Cli/Command/GenerateCachedConfigCommand.ts +2 -3
  61. package/src/Sindri/Cli/Command/GenerateDataFromConfigCommand.ts +2 -3
  62. package/src/Sindri/Constant/CommandName.ts +2 -3
  63. package/src/Sindri/Constant/SindriInfo.ts +4 -5
  64. package/src/Sindri/Constant/SindriServiceId.ts +2 -3
  65. package/src/Sindri/Generate/Abstract/GenerateCachedConfigFromAst.ts +2 -3
  66. package/src/Sindri/Generate/Abstract/GenerateDataFromAst.ts +39 -27
  67. package/src/Sindri/Generate/Abstract/GenerateFromAst.ts +2 -3
  68. package/src/Sindri/Generator/Abstract/AstFileGenerator.ts +2 -3
  69. package/src/Sindri/Generator/Abstract/FileGenerator.ts +2 -3
  70. package/src/Sindri/Generator/Ast/Cli/AstCliDataFileGenerator.ts +21 -6
  71. package/src/Sindri/Generator/Ast/Config/AstCachedConfigFileGenerator.ts +2 -3
  72. package/src/Sindri/Generator/Ast/Container/AstContainerDataFileGenerator.ts +2 -3
  73. package/src/Sindri/Generator/Ast/Event/AstEventDataFileGenerator.ts +2 -3
  74. package/src/Sindri/Generator/Ast/Http/AstHttpDataFileGenerator.ts +83 -45
  75. package/src/Sindri/Generator/Cli/Contract/CliDataFileGeneratorContract.ts +10 -3
  76. package/src/Sindri/Generator/Config/Contract/CachedConfigFileGeneratorContract.ts +2 -3
  77. package/src/Sindri/Generator/Container/Contract/ContainerDataFileGeneratorContract.ts +2 -3
  78. package/src/Sindri/Generator/Contract/FileGeneratorContract.ts +2 -3
  79. package/src/Sindri/Generator/Enum/GenerateStatus.ts +2 -3
  80. package/src/Sindri/Generator/Event/Contract/EventDataFileGeneratorContract.ts +2 -3
  81. package/src/Sindri/Generator/Http/Contract/HttpDataFileGeneratorContract.ts +11 -3
  82. package/src/Sindri/Generator/Throwable/Contract/GeneratorThrowable.ts +2 -3
  83. package/src/Sindri/Generator/Throwable/Exception/Abstract/GeneratorInvalidArgumentException.ts +2 -3
  84. package/src/Sindri/Generator/Throwable/Exception/Abstract/GeneratorRuntimeException.ts +2 -3
  85. package/src/Sindri/Generator/Throwable/Exception/GeneratorUnreachableException.ts +2 -3
  86. package/src/Sindri/Provider/SindriAstServiceProvider.ts +2 -3
  87. package/src/Sindri/Provider/SindriCliRouteProvider.ts +2 -3
  88. package/src/Sindri/Provider/SindriCommandServiceProvider.ts +2 -3
  89. package/src/Sindri/Provider/SindriComponentProvider.ts +2 -3
  90. package/src/Sindri/Throwable/Contract/SindriThrowable.ts +2 -3
  91. package/src/Sindri/Throwable/Exception/Abstract/SindriInvalidArgumentException.ts +2 -3
  92. package/src/Sindri/Throwable/Exception/Abstract/SindriRuntimeException.ts +2 -3
@@ -1,10 +1,9 @@
1
1
  /*
2
2
  * This file is part of the Sindri package.
3
3
  *
4
- * (c) Melech Mizrachi <melechmizrachi@gmail.com>
4
+ * Copyright (c) 2016-present Melech Mizrachi
5
5
  *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
6
+ * Released under the MIT License. See LICENSE.md for details.
8
7
  */
9
8
 
10
9
  import { ts } from 'ts-morph';
@@ -15,18 +14,22 @@ import { HttpRouteAttributeResult } from './Data/Result/HttpRouteAttributeResult
15
14
  import { HttpRouteMiddlewareReader } from './HttpRouteMiddlewareReader.ts';
16
15
  import { HttpRouteParameterReader } from './HttpRouteParameterReader.ts';
17
16
 
18
- import type { ClassDeclaration, Decorator, MethodDeclaration, Node } from 'ts-morph';
17
+ import type { ClassDeclaration, Decorator, MethodDeclaration } from 'ts-morph';
19
18
 
20
19
  import type { HttpRouteAttributeReaderContract } from './Contract/HttpRouteAttributeReaderContract.ts';
21
20
  import type { HttpRouteMiddlewareReaderContract } from './Contract/HttpRouteMiddlewareReaderContract.ts';
22
21
  import type { HttpRouteParameterReaderContract } from './Contract/HttpRouteParameterReaderContract.ts';
22
+ import type { HandlerData } from './Data/HandlerData.ts';
23
23
 
24
24
  /**
25
25
  * Scans an HTTP controller class file for @Route / @DynamicRoute and related
26
26
  * sub-decorators and returns TypeScript compiler API Expr nodes ready for the data cache generator.
27
27
  *
28
- * Mirrors the logic of the framework's runtime AttributeRouteCollector but operates
29
- * entirely on AST without executing any TypeScript code.
28
+ * The shipped decorators accept a single options object
29
+ * (`@Route({ path, name, ... })`), so this reader parses object-literal
30
+ * properties rather than positional arguments. It mirrors the logic of the
31
+ * framework's runtime AttributeRouteCollector but operates entirely on AST
32
+ * without executing any TypeScript code.
30
33
  */
31
34
  export class HttpRouteAttributeReader extends RouteAttributeReader implements HttpRouteAttributeReaderContract {
32
35
  constructor(
@@ -112,7 +115,7 @@ export class HttpRouteAttributeReader extends RouteAttributeReader implements Ht
112
115
 
113
116
  for (const method of classDecl.getMethods()) {
114
117
  for (const decorator of this.findDecoratorsOnNode(method, 'Route', useMap, currentFilePath)) {
115
- const data = this.buildRouteData(
118
+ this.collectRoute(
116
119
  decorator,
117
120
  method,
118
121
  useMap,
@@ -121,16 +124,13 @@ export class HttpRouteAttributeReader extends RouteAttributeReader implements Ht
121
124
  classPathPrefix,
122
125
  classNamePrefix,
123
126
  false,
127
+ routes,
128
+ routeData,
124
129
  );
125
-
126
- if (data !== null) {
127
- routes[data.name] = this.buildRouteExpr(data);
128
- routeData[data.name] = data;
129
- }
130
130
  }
131
131
 
132
132
  for (const decorator of this.findDecoratorsOnNode(method, 'DynamicRoute', useMap, currentFilePath)) {
133
- const data = this.buildRouteData(
133
+ this.collectRoute(
134
134
  decorator,
135
135
  method,
136
136
  useMap,
@@ -139,16 +139,44 @@ export class HttpRouteAttributeReader extends RouteAttributeReader implements Ht
139
139
  classPathPrefix,
140
140
  classNamePrefix,
141
141
  true,
142
+ routes,
143
+ routeData,
142
144
  );
143
-
144
- if (data !== null) {
145
- routes[data.name] = this.buildRouteExpr(data);
146
- routeData[data.name] = data;
147
- }
148
145
  }
149
146
  }
150
147
 
151
- return new HttpRouteAttributeResult(routes, routeData);
148
+ const importMap = this.buildImportMap(routeData, useMap, currentFilePath, currentClass);
149
+
150
+ return new HttpRouteAttributeResult(routes, routeData, importMap);
151
+ }
152
+
153
+ protected collectRoute(
154
+ decorator: Decorator,
155
+ method: MethodDeclaration,
156
+ useMap: Record<string, string>,
157
+ currentFilePath: string,
158
+ currentClass: string,
159
+ classPathPrefix: string,
160
+ classNamePrefix: string,
161
+ isDynamic: boolean,
162
+ routes: Record<string, ts.Expression>,
163
+ routeData: Record<string, HttpRouteData>,
164
+ ): void {
165
+ const data = this.buildRouteData(
166
+ decorator,
167
+ method,
168
+ useMap,
169
+ currentFilePath,
170
+ currentClass,
171
+ classPathPrefix,
172
+ classNamePrefix,
173
+ isDynamic,
174
+ );
175
+
176
+ if (data !== null) {
177
+ routes[data.name] = this.buildRouteExpr(data);
178
+ routeData[data.name] = data;
179
+ }
152
180
  }
153
181
 
154
182
  protected buildRouteData(
@@ -161,8 +189,14 @@ export class HttpRouteAttributeReader extends RouteAttributeReader implements Ht
161
189
  classNamePrefix: string,
162
190
  isDynamic: boolean,
163
191
  ): HttpRouteData | null {
164
- const path = this.extractStringArg(decorator, 0, useMap, currentFilePath, currentClass);
165
- const name = this.extractStringArg(decorator, 1, useMap, currentFilePath, currentClass);
192
+ const obj = this.getDecoratorObjectArg(decorator);
193
+
194
+ if (obj === undefined) {
195
+ return null;
196
+ }
197
+
198
+ const path = this.getObjectStringProp(obj, 'path', useMap, currentFilePath, currentClass);
199
+ const name = this.getObjectStringProp(obj, 'name', useMap, currentFilePath, currentClass);
166
200
 
167
201
  if (path === '' || name === '') {
168
202
  return null;
@@ -173,10 +207,8 @@ export class HttpRouteAttributeReader extends RouteAttributeReader implements Ht
173
207
 
174
208
  const resolvedIsDynamic = isDynamic || updatedPath.includes('{');
175
209
 
176
- const decoratorArgs = decorator.getArguments().map((a: Node) => a.compilerNode as ts.Expression);
177
-
178
210
  const requestMethods = this.middlewareReader.updateRequestMethods(
179
- this.middlewareReader.extractInlineRequestMethods(decoratorArgs, useMap, currentFilePath, currentClass),
211
+ this.middlewareReader.extractObjectRequestMethods(obj, useMap, currentFilePath, currentClass),
180
212
  method,
181
213
  useMap,
182
214
  currentFilePath,
@@ -194,32 +226,84 @@ export class HttpRouteAttributeReader extends RouteAttributeReader implements Ht
194
226
  useMap,
195
227
  currentFilePath,
196
228
  currentClass,
197
- this.extractClassListArgFromDecorator(decorator, 5, useMap, currentFilePath, currentClass),
198
- this.extractClassListArgFromDecorator(decorator, 6, useMap, currentFilePath, currentClass),
199
- this.extractClassListArgFromDecorator(decorator, 7, useMap, currentFilePath, currentClass),
200
- this.extractClassListArgFromDecorator(decorator, 8, useMap, currentFilePath, currentClass),
201
- this.extractClassListArgFromDecorator(decorator, 9, useMap, currentFilePath, currentClass),
229
+ this.getObjectClassListProp(obj, 'middleware', useMap, currentFilePath, currentClass),
202
230
  );
203
231
 
204
232
  return new HttpRouteData(
205
233
  updatedPath,
206
234
  updatedName,
207
- this.updateHandler(method, useMap, currentFilePath, currentClass),
235
+ this.resolveHandler(obj, method, useMap, currentFilePath, currentClass),
208
236
  requestMethods,
209
237
  routeMatchedMiddleware,
210
238
  routeDispatchedMiddleware,
211
239
  throwableCaughtMiddleware,
212
240
  sendingResponseMiddleware,
213
241
  responseSentMiddleware,
214
- this.middlewareReader.updateRequestStruct(method, useMap, currentFilePath, currentClass),
215
- this.middlewareReader.updateResponseStruct(method, useMap, currentFilePath, currentClass),
242
+ this.resolveRequestStruct(obj, method, useMap, currentFilePath, currentClass),
243
+ this.resolveResponseStruct(obj, method, useMap, currentFilePath, currentClass),
216
244
  resolvedIsDynamic,
217
- resolvedIsDynamic
218
- ? this.updateParameters(decoratorArgs, method, useMap, currentFilePath, currentClass)
219
- : [],
245
+ resolvedIsDynamic ? this.parameterReader.updateParameters(obj, useMap, currentFilePath, currentClass) : [],
220
246
  );
221
247
  }
222
248
 
249
+ protected resolveHandler(
250
+ obj: ts.ObjectLiteralExpression,
251
+ method: MethodDeclaration,
252
+ useMap: Record<string, string>,
253
+ currentFilePath: string,
254
+ currentClass: string,
255
+ ): HandlerData {
256
+ const fromObj = this.getObjectHandlerProp(obj, 'handler', useMap, currentFilePath, currentClass);
257
+
258
+ if (fromObj !== undefined) {
259
+ return fromObj;
260
+ }
261
+
262
+ return this.updateHandler(method, useMap, currentFilePath, currentClass);
263
+ }
264
+
265
+ protected resolveRequestStruct(
266
+ obj: ts.ObjectLiteralExpression,
267
+ method: MethodDeclaration,
268
+ useMap: Record<string, string>,
269
+ currentFilePath: string,
270
+ currentClass: string,
271
+ ): string | null {
272
+ const value = this.extractExprValue(
273
+ this.getObjectProp(obj, 'requestStruct'),
274
+ useMap,
275
+ currentFilePath,
276
+ currentClass,
277
+ );
278
+
279
+ if (typeof value === 'string' && value !== '') {
280
+ return value;
281
+ }
282
+
283
+ return this.middlewareReader.updateRequestStruct(method, useMap, currentFilePath, currentClass);
284
+ }
285
+
286
+ protected resolveResponseStruct(
287
+ obj: ts.ObjectLiteralExpression,
288
+ method: MethodDeclaration,
289
+ useMap: Record<string, string>,
290
+ currentFilePath: string,
291
+ currentClass: string,
292
+ ): string | null {
293
+ const value = this.extractExprValue(
294
+ this.getObjectProp(obj, 'responseStruct'),
295
+ useMap,
296
+ currentFilePath,
297
+ currentClass,
298
+ );
299
+
300
+ if (typeof value === 'string' && value !== '') {
301
+ return value;
302
+ }
303
+
304
+ return this.middlewareReader.updateResponseStruct(method, useMap, currentFilePath, currentClass);
305
+ }
306
+
223
307
  protected updatePath(
224
308
  path: string,
225
309
  classPathPrefix: string,
@@ -276,23 +360,27 @@ export class HttpRouteAttributeReader extends RouteAttributeReader implements Ht
276
360
  return name;
277
361
  }
278
362
 
363
+ /**
364
+ * Emit `new DynamicRoute(path, name, regex, parameters, handler,
365
+ * requestMethods, ...5 middleware, requestStruct, responseStruct)` or
366
+ * `new Route(path, name, handler, requestMethods, ...5 middleware,
367
+ * requestStruct, responseStruct)`, matching the framework constructor order.
368
+ *
369
+ * For dynamic routes the regex slot is emitted as an empty-string
370
+ * placeholder; the generator computes and injects the real regex (it owns
371
+ * the routing `Processor`), and the parameters array is always emitted (`[]`
372
+ * when empty).
373
+ */
279
374
  protected buildRouteExpr(data: HttpRouteData): ts.Expression {
280
375
  const args: ts.Expression[] = [this.buildEnumCaseExpr(data.path), this.buildEnumCaseExpr(data.name)];
281
376
 
282
- if (data.isDynamic && data.parameters.length > 0) {
377
+ if (data.isDynamic) {
378
+ args.push(this.buildStringExpr(''));
283
379
  args.push(this.parameterReader.buildParameterListExpr([...data.parameters]));
284
380
  }
285
381
 
286
- if (data.handler !== null) {
287
- args.push(this.buildHandlerExpr(data.handler));
288
- } else {
289
- args.push(this.buildNullExpr());
290
- }
291
-
292
- if (data.requestMethods.length > 0) {
293
- args.push(this.buildEnumCaseArrayExpr(data.requestMethods));
294
- }
295
-
382
+ args.push(data.handler !== null ? this.buildHandlerExpr(data.handler) : this.buildNullExpr());
383
+ args.push(this.buildEnumCaseArrayExpr(data.requestMethods));
296
384
  args.push(...this.middlewareReader.buildRouteMiddlewareArgs(data));
297
385
  args.push(...this.middlewareReader.buildRouteStructArgs(data));
298
386
 
@@ -303,13 +391,54 @@ export class HttpRouteAttributeReader extends RouteAttributeReader implements Ht
303
391
  return this.buildNewExpr(targetClass, args);
304
392
  }
305
393
 
306
- protected updateParameters(
307
- decoratorArgs: ts.Expression[],
308
- method: MethodDeclaration,
394
+ /**
395
+ * Build the class-name → absolute-file-path map for every handler and
396
+ * middleware class the generated data cache references, so the generator can
397
+ * emit the matching import statements.
398
+ */
399
+ protected buildImportMap(
400
+ routeData: Record<string, HttpRouteData>,
309
401
  useMap: Record<string, string>,
310
402
  currentFilePath: string,
311
403
  currentClass: string,
312
- ): import('./Data/HttpParameterData.ts').HttpParameterData[] {
313
- return this.parameterReader.updateParameters(decoratorArgs, method, useMap, currentFilePath, currentClass);
404
+ ): Record<string, string> {
405
+ const importMap: Record<string, string> = {};
406
+
407
+ for (const data of Object.values(routeData)) {
408
+ if (data.handler !== null) {
409
+ this.addClassImport(importMap, data.handler.class, useMap, currentFilePath, currentClass);
410
+ }
411
+
412
+ for (const middleware of [
413
+ ...data.routeMatchedMiddleware,
414
+ ...data.routeDispatchedMiddleware,
415
+ ...data.throwableCaughtMiddleware,
416
+ ...data.sendingResponseMiddleware,
417
+ ...data.responseSentMiddleware,
418
+ ]) {
419
+ this.addClassImport(importMap, middleware, useMap, currentFilePath, currentClass);
420
+ }
421
+ }
422
+
423
+ return importMap;
424
+ }
425
+
426
+ protected addClassImport(
427
+ importMap: Record<string, string>,
428
+ className: string,
429
+ useMap: Record<string, string>,
430
+ currentFilePath: string,
431
+ currentClass: string,
432
+ ): void {
433
+ const shortName = className.slice(className.lastIndexOf('\\') + 1);
434
+
435
+ const filePath =
436
+ shortName === currentClass
437
+ ? currentFilePath
438
+ : this.resolveImportToFilePath(shortName, useMap, currentFilePath);
439
+
440
+ if (filePath !== '') {
441
+ importMap[shortName] = filePath;
442
+ }
314
443
  }
315
444
  }
@@ -1,10 +1,9 @@
1
1
  /*
2
2
  * This file is part of the Sindri package.
3
3
  *
4
- * (c) Melech Mizrachi <melechmizrachi@gmail.com>
4
+ * Copyright (c) 2016-present Melech Mizrachi
5
5
  *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
6
+ * Released under the MIT License. See LICENSE.md for details.
8
7
  */
9
8
 
10
9
  import { ts } from 'ts-morph';
@@ -24,19 +23,13 @@ import type { HttpRouteMiddlewareReaderContract } from './Contract/HttpRouteMidd
24
23
  * complexity threshold; injected as a constructor argument.
25
24
  */
26
25
  export class HttpRouteMiddlewareReader extends AstReader implements HttpRouteMiddlewareReaderContract {
27
- extractInlineRequestMethods(
28
- decoratorArgs: ts.NodeArray<ts.Expression> | ts.Expression[],
26
+ extractObjectRequestMethods(
27
+ obj: ts.ObjectLiteralExpression,
29
28
  useMap: Record<string, string>,
30
29
  namespace: string,
31
30
  currentClass: string,
32
31
  ): string[] {
33
- const requestMethodsArg = decoratorArgs[3];
34
-
35
- if (requestMethodsArg === undefined || !ts.isArrayLiteralExpression(requestMethodsArg)) {
36
- return [];
37
- }
38
-
39
- return this.extractClassListFromArrayExpr(requestMethodsArg, useMap, namespace, currentClass);
32
+ return this.getObjectClassListProp(obj, 'requestMethods', useMap, namespace, currentClass);
40
33
  }
41
34
 
42
35
  updateRequestMethods(
@@ -71,14 +64,41 @@ export class HttpRouteMiddlewareReader extends AstReader implements HttpRouteMid
71
64
  useMap: Record<string, string>,
72
65
  namespace: string,
73
66
  currentClass: string,
74
- routeMatchedMiddleware: string[],
75
- routeDispatchedMiddleware: string[],
76
- throwableCaughtMiddleware: string[],
77
- sendingResponseMiddleware: string[],
78
- responseSentMiddleware: string[],
67
+ middleware: string[],
79
68
  ): [string[], string[], string[], string[], string[]] {
69
+ let routeMatchedMiddleware: string[] = [];
70
+ let routeDispatchedMiddleware: string[] = [];
71
+ let throwableCaughtMiddleware: string[] = [];
72
+ let sendingResponseMiddleware: string[] = [];
73
+ let responseSentMiddleware: string[] = [];
74
+
75
+ for (const mwName of middleware) {
76
+ [
77
+ routeMatchedMiddleware,
78
+ routeDispatchedMiddleware,
79
+ throwableCaughtMiddleware,
80
+ sendingResponseMiddleware,
81
+ responseSentMiddleware,
82
+ ] = this.classifyMiddleware(
83
+ mwName,
84
+ useMap,
85
+ namespace,
86
+ routeMatchedMiddleware,
87
+ routeDispatchedMiddleware,
88
+ throwableCaughtMiddleware,
89
+ sendingResponseMiddleware,
90
+ responseSentMiddleware,
91
+ );
92
+ }
93
+
80
94
  for (const decorator of this.findDecoratorsOnNode(method, 'Middleware', useMap, namespace)) {
81
- const mwName = this.extractExprValue(this.getDecoratorArg(decorator, 0), useMap, namespace, currentClass);
95
+ // `@Middleware` takes a thunked class reference (`() => AuthMiddleware`).
96
+ const mwName = this.extractExprValue(
97
+ this.unwrapClassThunk(this.getDecoratorArg(decorator, 0)),
98
+ useMap,
99
+ namespace,
100
+ currentClass,
101
+ );
82
102
 
83
103
  if (typeof mwName !== 'string' || mwName === '') {
84
104
  continue;
@@ -1,10 +1,9 @@
1
1
  /*
2
2
  * This file is part of the Sindri package.
3
3
  *
4
- * (c) Melech Mizrachi <melechmizrachi@gmail.com>
4
+ * Copyright (c) 2016-present Melech Mizrachi
5
5
  *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
6
+ * Released under the MIT License. See LICENSE.md for details.
8
7
  */
9
8
 
10
9
  import { ts } from 'ts-morph';
@@ -12,75 +11,41 @@ import { ts } from 'ts-morph';
12
11
  import { AstReader } from './Abstract/AstReader.ts';
13
12
  import { HttpParameterData } from './Data/HttpParameterData.ts';
14
13
 
15
- import type { Decorator, MethodDeclaration } from 'ts-morph';
16
-
17
14
  import type { HttpRouteParameterReaderContract } from './Contract/HttpRouteParameterReaderContract.ts';
18
15
 
19
16
  /**
20
17
  * Reads and builds AST expressions for HTTP dynamic route parameters.
21
18
  *
19
+ * The shipped `@DynamicRoute` decorator folds parameter definitions into its
20
+ * options object as `parameters: [{ name, regex, cast?, isOptional?,
21
+ * shouldCapture?, default? }]` (TC39 Stage-3 has no parameter decorators), so
22
+ * this reader parses that array of object literals rather than a separate
23
+ * `@Parameter` decorator.
24
+ *
22
25
  * Extracted from HttpRouteAttributeReader to keep each class under the
23
26
  * complexity threshold; injected as a constructor argument.
24
27
  */
25
28
  export class HttpRouteParameterReader extends AstReader implements HttpRouteParameterReaderContract {
26
29
  updateParameters(
27
- decoratorArgs: ts.NodeArray<ts.Expression> | ts.Expression[],
28
- method: MethodDeclaration,
30
+ obj: ts.ObjectLiteralExpression,
29
31
  useMap: Record<string, string>,
30
32
  namespace: string,
31
33
  currentClass: string,
32
34
  ): HttpParameterData[] {
33
- return [
34
- ...this.collectInlineParameters(decoratorArgs, useMap, namespace, currentClass),
35
- ...this.collectAttributeParameters(method, useMap, namespace, currentClass),
36
- ...this.collectMethodParamParameters(method, useMap, namespace, currentClass),
37
- ];
38
- }
35
+ const node = this.getObjectProp(obj, 'parameters');
39
36
 
40
- buildParameterListExpr(parameters: HttpParameterData[]): ts.ArrayLiteralExpression {
41
- const elements = parameters.map((p) => this.buildParameterExpr(p));
42
- return ts.factory.createArrayLiteralExpression(elements);
43
- }
44
-
45
- protected collectInlineParameters(
46
- decoratorArgs: ts.NodeArray<ts.Expression> | ts.Expression[],
47
- useMap: Record<string, string>,
48
- currentFilePath: string,
49
- currentClass: string,
50
- ): HttpParameterData[] {
51
- const inlineArg = decoratorArgs[2];
52
-
53
- if (inlineArg === undefined || !ts.isArrayLiteralExpression(inlineArg)) {
37
+ if (node === undefined || !ts.isArrayLiteralExpression(node)) {
54
38
  return [];
55
39
  }
56
40
 
57
41
  const parameters: HttpParameterData[] = [];
58
42
 
59
- for (const element of inlineArg.elements) {
60
- if (!ts.isNewExpression(element) || element.arguments === undefined) {
43
+ for (const element of node.elements) {
44
+ if (!ts.isObjectLiteralExpression(element)) {
61
45
  continue;
62
46
  }
63
47
 
64
- const param = this.buildParameterDataFromTsArgs(element.arguments, useMap, currentFilePath, currentClass);
65
-
66
- if (param !== null) {
67
- parameters.push(param);
68
- }
69
- }
70
-
71
- return parameters;
72
- }
73
-
74
- protected collectAttributeParameters(
75
- method: MethodDeclaration,
76
- useMap: Record<string, string>,
77
- currentFilePath: string,
78
- currentClass: string,
79
- ): HttpParameterData[] {
80
- const parameters: HttpParameterData[] = [];
81
-
82
- for (const decorator of this.findDecoratorsOnNode(method, 'Parameter', useMap, currentFilePath)) {
83
- const param = this.buildParameterDataFromDecorator(decorator, useMap, currentFilePath, currentClass);
48
+ const param = this.buildParameterData(element, useMap, namespace, currentClass);
84
49
 
85
50
  if (param !== null) {
86
51
  parameters.push(param);
@@ -90,89 +55,49 @@ export class HttpRouteParameterReader extends AstReader implements HttpRoutePara
90
55
  return parameters;
91
56
  }
92
57
 
93
- protected collectMethodParamParameters(
94
- method: MethodDeclaration,
95
- useMap: Record<string, string>,
96
- currentFilePath: string,
97
- currentClass: string,
98
- ): HttpParameterData[] {
99
- const parameters: HttpParameterData[] = [];
100
-
101
- for (const methodParam of method.getParameters()) {
102
- for (const decorator of this.findDecoratorsOnNode(methodParam, 'Parameter', useMap, currentFilePath)) {
103
- const param = this.buildParameterDataFromDecorator(decorator, useMap, currentFilePath, currentClass);
104
-
105
- if (param !== null) {
106
- parameters.push(param);
107
- }
108
- }
109
- }
110
-
111
- return parameters;
58
+ buildParameterListExpr(parameters: HttpParameterData[]): ts.ArrayLiteralExpression {
59
+ const elements = parameters.map((p) => this.buildParameterExpr(p));
60
+ return ts.factory.createArrayLiteralExpression(elements);
112
61
  }
113
62
 
114
- protected buildParameterDataFromDecorator(
115
- decorator: Decorator,
63
+ protected buildParameterData(
64
+ obj: ts.ObjectLiteralExpression,
116
65
  useMap: Record<string, string>,
117
66
  currentFilePath: string,
118
67
  currentClass: string,
119
68
  ): HttpParameterData | null {
120
- const name = this.extractStringArg(decorator, 0, useMap, currentFilePath, currentClass);
121
- const regex = this.extractStringArg(decorator, 1, useMap, currentFilePath, currentClass);
69
+ const name = this.getObjectStringProp(obj, 'name', useMap, currentFilePath, currentClass);
70
+ const regex = this.getObjectStringProp(obj, 'regex', useMap, currentFilePath, currentClass);
122
71
 
123
72
  if (name === '' || regex === '') {
124
73
  return null;
125
74
  }
126
75
 
76
+ const cast = this.getObjectStringProp(obj, 'cast', useMap, currentFilePath, currentClass) || null;
77
+
127
78
  return new HttpParameterData(
128
79
  name,
129
80
  regex,
130
- this.extractStringArg(decorator, 2, useMap, currentFilePath, currentClass) || null,
131
- this.extractBoolArg(decorator, 3, useMap, currentFilePath, currentClass, false),
132
- this.extractBoolArg(decorator, 4, useMap, currentFilePath, currentClass, true),
81
+ cast,
82
+ this.getObjectBoolProp(obj, 'isOptional', useMap, currentFilePath, currentClass, false),
83
+ this.getObjectBoolProp(obj, 'shouldCapture', useMap, currentFilePath, currentClass, true),
84
+ this.extractDefault(obj, useMap, currentFilePath, currentClass),
133
85
  );
134
86
  }
135
87
 
136
- protected buildParameterDataFromTsArgs(
137
- args: ts.NodeArray<ts.Expression>,
88
+ protected extractDefault(
89
+ obj: ts.ObjectLiteralExpression,
138
90
  useMap: Record<string, string>,
139
91
  currentFilePath: string,
140
92
  currentClass: string,
141
- ): HttpParameterData | null {
142
- const nameNode = args[0];
143
- const regexNode = args[1];
93
+ ): string | number | boolean | null {
94
+ const value = this.extractExprValue(this.getObjectProp(obj, 'default'), useMap, currentFilePath, currentClass);
144
95
 
145
- if (nameNode === undefined || regexNode === undefined) {
146
- return null;
96
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
97
+ return value;
147
98
  }
148
99
 
149
- const name = this.extractExprValue(nameNode, useMap, currentFilePath, currentClass);
150
- const regex = this.extractExprValue(regexNode, useMap, currentFilePath, currentClass);
151
-
152
- if (typeof name !== 'string' || name === '' || typeof regex !== 'string' || regex === '') {
153
- return null;
154
- }
155
-
156
- const castNode = args[2];
157
- const castRaw =
158
- castNode !== undefined ? this.extractExprValue(castNode, useMap, currentFilePath, currentClass) : null;
159
- const cast = typeof castRaw === 'string' && castRaw !== '' ? castRaw : null;
160
-
161
- const isOptionalNode = args[3];
162
- const isOptionalRaw =
163
- isOptionalNode !== undefined
164
- ? this.extractExprValue(isOptionalNode, useMap, currentFilePath, currentClass)
165
- : false;
166
- const isOptional = typeof isOptionalRaw === 'boolean' ? isOptionalRaw : false;
167
-
168
- const shouldCaptureNode = args[4];
169
- const shouldCaptureRaw =
170
- shouldCaptureNode !== undefined
171
- ? this.extractExprValue(shouldCaptureNode, useMap, currentFilePath, currentClass)
172
- : true;
173
- const shouldCapture = typeof shouldCaptureRaw === 'boolean' ? shouldCaptureRaw : true;
174
-
175
- return new HttpParameterData(name, regex, cast, isOptional, shouldCapture);
100
+ return null;
176
101
  }
177
102
 
178
103
  protected buildParameterExpr(data: HttpParameterData): ts.Expression {
@@ -188,6 +113,22 @@ export class HttpRouteParameterReader extends AstReader implements HttpRoutePara
188
113
  this.buildBoolExpr(data.shouldCapture),
189
114
  ];
190
115
 
116
+ if (data.defaultValue !== null) {
117
+ args.push(this.buildScalarExpr(data.defaultValue));
118
+ }
119
+
191
120
  return this.buildNewExpr('Valkyrja\\Http\\Routing\\Data\\Parameter', args);
192
121
  }
122
+
123
+ protected buildScalarExpr(value: string | number | boolean): ts.Expression {
124
+ if (typeof value === 'string') {
125
+ return this.buildStringExpr(value);
126
+ }
127
+
128
+ if (typeof value === 'boolean') {
129
+ return this.buildBoolExpr(value);
130
+ }
131
+
132
+ return ts.factory.createNumericLiteral(value);
133
+ }
193
134
  }
@@ -1,10 +1,9 @@
1
1
  /*
2
2
  * This file is part of the Sindri package.
3
3
  *
4
- * (c) Melech Mizrachi <melechmizrachi@gmail.com>
4
+ * Copyright (c) 2016-present Melech Mizrachi
5
5
  *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
6
+ * Released under the MIT License. See LICENSE.md for details.
8
7
  */
9
8
 
10
9
  import { ts } from 'ts-morph';