@necessarylion/adonis-autoswagger 0.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Adis Durakovic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,586 @@
1
+ <h1 align="center">
2
+ Adonis AutoSwagger <br />
3
+ <img src="https://upload.wikimedia.org/wikipedia/commons/a/ab/Swagger-logo.png" height="50" />
4
+ </h1>
5
+
6
+ [![Version](https://img.shields.io/github/tag/necessarylion/adonis-autoswagger.svg?style=flat?branch=main)]()
7
+ [![GitHub stars](https://img.shields.io/github/stars/necessarylion/adonis-autoswagger.svg?style=social&label=Star)]()
8
+ [![GitHub watchers](https://img.shields.io/github/watchers/necessarylion/adonis-autoswagger.svg?style=social&label=Watch)]()
9
+ [![GitHub forks](https://img.shields.io/github/forks/necessarylion/adonis-autoswagger.svg?style=social&label=Fork)]()
10
+
11
+ ### Auto-Generate swagger docs for AdonisJS
12
+
13
+ ## 💻️ Install
14
+
15
+ ```bash
16
+ pnpm i adonis-autoswagger #using pnpm
17
+ ```
18
+
19
+ ---
20
+
21
+ ## ⭐️ Features
22
+
23
+ - Creates **paths** automatically based on `routes.ts`
24
+ - Creates **schemas** automatically based on `app/Models/*`
25
+ - Creates **schemas** automatically based on `app/Interfaces/*`
26
+ - Creates **schemas** automatically based on `app/Validators/*` (only for adonisJS v6)
27
+ - Creates **schemas** automatically based on `app/Types/*` (only for adonisJS v6)
28
+ - **Rich configuration** via comments
29
+ - Works also in **production** mode
30
+ - `node ace docs:generate` command
31
+
32
+ ---
33
+
34
+ ## ✌️Usage
35
+
36
+ Create a file `/config/swagger.ts`
37
+
38
+ ```ts
39
+ // for AdonisJS v6
40
+ import path from "node:path";
41
+ import url from "node:url";
42
+ // ---
43
+
44
+ export default {
45
+ // path: __dirname + "/../", for AdonisJS v5
46
+ path: path.dirname(url.fileURLToPath(import.meta.url)) + "/../", // for AdonisJS v6
47
+ title: "Foo", // use info instead
48
+ version: "1.0.0", // use info instead
49
+ description: "", // use info instead
50
+ tagIndex: 2,
51
+ productionEnv: "production", // optional
52
+ info: {
53
+ title: "title",
54
+ version: "1.0.0",
55
+ description: "",
56
+ },
57
+ snakeCase: true,
58
+
59
+ debug: false, // set to true, to get some useful debug output
60
+ ignore: ["/swagger", "/docs"],
61
+ preferredPutPatch: "PUT", // if PUT/PATCH are provided for the same route, prefer PUT
62
+ common: {
63
+ parameters: {}, // OpenAPI conform parameters that are commonly used
64
+ headers: {}, // OpenAPI conform headers that are commonly used
65
+ },
66
+ securitySchemes: {}, // optional
67
+ authMiddlewares: ["auth", "auth:api"], // optional
68
+ defaultSecurityScheme: "BearerAuth", // optional
69
+ persistAuthorization: true, // persist authorization between reloads on the swagger page
70
+ showFullPath: false, // the path displayed after endpoint summary
71
+ };
72
+ ```
73
+
74
+ In your `routes.ts`
75
+
76
+ ## 6️⃣ for AdonisJS v6
77
+
78
+ ```js
79
+ import AutoSwagger from "adonis-autoswagger";
80
+ import swagger from "#config/swagger";
81
+ // returns swagger in YAML
82
+ router.get("/swagger", async () => {
83
+ return AutoSwagger.default.docs(router.toJSON(), swagger);
84
+ });
85
+
86
+ // Renders Swagger-UI and passes YAML-output of /swagger
87
+ router.get("/docs", async () => {
88
+ return AutoSwagger.default.ui("/swagger", swagger);
89
+ // return AutoSwagger.default.scalar("/swagger"); to use Scalar instead. If you want, you can pass proxy url as second argument here.
90
+ // return AutoSwagger.default.rapidoc("/swagger", "view"); to use RapiDoc instead (pass "view" default, or "read" to change the render-style)
91
+ });
92
+ ```
93
+
94
+ ## 5️⃣ for AdonisJS v5
95
+
96
+ ```js
97
+ import AutoSwagger from "adonis-autoswagger";
98
+ import swagger from "Config/swagger";
99
+ // returns swagger in YAML
100
+ Route.get("/swagger", async () => {
101
+ return AutoSwagger.docs(Route.toJSON(), swagger);
102
+ });
103
+
104
+ // Renders Swagger-UI and passes YAML-output of /swagger
105
+ Route.get("/docs", async () => {
106
+ return AutoSwagger.ui("/swagger", swagger);
107
+ });
108
+ ```
109
+
110
+ ### 👍️ Done
111
+
112
+ Visit `http://localhost:3333/docs` to see AutoSwagger in action.
113
+
114
+ ### Functions
115
+
116
+ - `async docs(routes, conf)`: get the specification in YAML format
117
+ - `async json(routes, conf)`: get the specification in JSON format
118
+ - `ui(path, conf)`: get default swagger UI
119
+ - `rapidoc(path, style)`: get rapidoc UI
120
+ - `scalar(path, proxyUrl)`: get scalar UI
121
+ - `stoplight(path, theme)`: get stoplight elements UI
122
+ - `jsonToYaml(json)`: can be used to convert `json()` back to yaml
123
+
124
+ ---
125
+
126
+ ## 💡 Compatibility
127
+
128
+ For controllers to get detected properly, please load them lazily.
129
+
130
+ ```ts
131
+ ✅ const TestController = () => import('#controllers/test_controller')
132
+ ❌ import TestController from '#controllers/test_controller'
133
+ ```
134
+
135
+ ## 🧑‍💻 Advanced usage
136
+
137
+ ### Additional configuration
138
+
139
+ **info**
140
+ See [Swagger API General Info](https://swagger.io/docs/specification/api-general-info/) for details.
141
+
142
+ **securitySchemes**
143
+
144
+ Add/Overwrite security schemes [Swagger Authentication](https://swagger.io/docs/specification/authentication/) for details.
145
+
146
+ ```ts
147
+ // example to override ApiKeyAuth
148
+ securitySchemes: {
149
+ ApiKeyAuth: {
150
+ type: "apiKey"
151
+ in: "header",
152
+ name: "X-API-Key"
153
+ }
154
+ }
155
+ ```
156
+
157
+ **defaultSecurityScheme**
158
+
159
+ Override the default security scheme.
160
+
161
+ - BearerAuth
162
+ - BasicAuth
163
+ - ApiKeyAuth
164
+ - your own defined under `securitySchemes`
165
+
166
+ **authMiddlewares**
167
+
168
+ If a route uses a middleware named `auth`, `auth:api`, AutoSwagger will detect it as a Swagger security method. However, you can implement other middlewares that handle authentication.
169
+
170
+ ### Modify generated output
171
+
172
+ ```ts
173
+ Route.get("/myswagger", async () => {
174
+ const json = await AutoSwagger.json(Route.toJSON(), swagger);
175
+ // modify json to your hearts content
176
+ return AutoSwagger.jsonToYaml(json);
177
+ });
178
+
179
+ Route.get("/docs", async () => {
180
+ return AutoSwagger.ui("/myswagger", swagger);
181
+ });
182
+ ```
183
+
184
+ ### Custom Paths in adonisJS v6
185
+
186
+ AutoSwagger supports the paths set in `package.json`. Interfaces are expected to be in `app/interfaces`. However, you can override this, by modifying package.json as follows.
187
+
188
+ ```json
189
+ //...
190
+ "imports": {
191
+ // ...
192
+ "#interfaces/*": "./app/custom/path/interfaces/*.js"
193
+ // ...
194
+ }
195
+ //...
196
+
197
+ ```
198
+
199
+ ---
200
+
201
+ ## 📃 Configure
202
+
203
+ ### `tagIndex`
204
+
205
+ Tags endpoints automatically
206
+
207
+ - If your routes are `/api/v1/products/...` then your tagIndex should be `3`
208
+ - If your routes are `/v1/products/...` then your tagIndex should be `2`
209
+ - If your routes are `/products/...` then your tagIndex should be `1`
210
+
211
+ ### `ignore`
212
+
213
+ Ignores specified paths. When used with a wildcard (\*), AutoSwagger will ignore everything matching before/after the wildcard.
214
+ `/test/_`will ignore everything starting with`/test/`, whereas `\*/test`will ignore everything ending with`/test`.
215
+
216
+ ### `common`
217
+
218
+ Sometimes you want to use specific parameters or headers on multiple responses.
219
+
220
+ _Example:_ Some resources use the same filter parameters or return the same headers.
221
+
222
+ Here's where you can set these and use them with `@paramUse()` and `@responseHeader() @use()`. See practical example for further details.
223
+
224
+ ---
225
+
226
+ # 💫 Extend Controllers
227
+
228
+ ## Add additional documentation to your Controller-files
229
+
230
+ **@summary** (only one)
231
+ A summary of what the action does
232
+
233
+ **@tag** (only one)
234
+ Set a custom tag for this action
235
+
236
+ **@description** (only one)
237
+ A detailed description of what the action does.
238
+
239
+ **@operationId** (only one)
240
+ An optional unique string used to identify an operation. If provided, these IDs must be unique among all operations described in your API..
241
+
242
+ **@responseBody** (multiple)
243
+
244
+ Format: `<status> - <return> - <description>`
245
+
246
+ `<return>` can be either a `<Schema>`, `<Schema[]>/` or a custom JSON `{}`
247
+
248
+ **@responseHeader** (multiple)
249
+
250
+ Format: `<status> - <name> - <description> - <meta>`
251
+
252
+ **@param`Type`** (multiple)
253
+
254
+ `Type` can be one of [Parameter Types](https://swagger.io/docs/specification/describing-parameters/) (first letter in uppercase)
255
+
256
+ **@requestBody** (only one)
257
+ A definition of the expected requestBody
258
+
259
+ Format: `<body>`
260
+
261
+ `<body>` can be either a `<Schema>`, `<Schema[]>/`, or a custom JSON `{}`
262
+
263
+ **@requestFormDataBody** (only one)
264
+ A definition of the expected requestBody that will be sent with formData format.
265
+
266
+ **Schema**
267
+ A model or a validator.
268
+ Format: `<Schema>`
269
+
270
+ **Custom format**
271
+
272
+ Format: `{"fieldname": {"type":"string", "format": "email"}}`
273
+ This format should be a valid openapi 3.x json.
274
+
275
+ ---
276
+
277
+ # 🤘Examples
278
+
279
+ ## `@responseBody` examples
280
+
281
+ ```ts
282
+ @responseBody <status> - Lorem ipsum Dolor sit amet
283
+
284
+ @responseBody <status> // returns standard <status> message
285
+
286
+ @responseBody <status> - <Model> // returns model specification
287
+
288
+ @responseBody <status> - <Model[]> // returns model-array specification
289
+
290
+ @responseBody <status> - <Model>.with(relations, property1, property2.relations, property3.subproperty.relations) // returns a model and a defined relation
291
+
292
+ @responseBody <status> - <Model[]>.with(relations).exclude(property1, property2, property3.subproperty) // returns model specification
293
+
294
+ @responseBody <status> - <Model[]>.append("some":"valid json") // append additional properties to a Model
295
+
296
+ @responseBody <status> - <Model[]>.paginated() // helper function to return adonisJS conform structure like {"data": [], "meta": {}}
297
+
298
+ @responseBody <status> - <Model[]>.paginated(dataName, metaName) // returns a paginated model with custom keys for the data array and meta object, use `.paginated(dataName)` or `.paginated(,metaName)` if you want to override only one. Don't forget the ',' for the second parameter.
299
+
300
+ @responseBody <status> - <Model>.only(property1, property2) // pick only specific properties
301
+
302
+ @requestBody <status> <myCustomValidator> // returns a validator object
303
+
304
+ @responseBody <status> - {"foo": "bar", "baz": "<Model>"} //returns custom json object and also parses the model
305
+ @responseBody <status> - ["foo", "bar"] //returns custom json array
306
+ ```
307
+
308
+ ## `@paramPath` and `@paramQuery` examples
309
+
310
+ ```ts
311
+ // basicaly same as @response, just without a status
312
+ @paramPath <paramName> - Description - (meta)
313
+ @paramQuery <paramName> - Description - (meta)
314
+
315
+ @paramPath id - The ID of the source - @type(number) @required
316
+ @paramPath slug - The ID of the source - @type(string)
317
+
318
+ @paramQuery q - Search term - @type(string) @required
319
+ @paramQuery page - the Page number - @type(number)
320
+
321
+ ```
322
+
323
+ ## `@requestBody` examples
324
+
325
+ ```ts
326
+ // basicaly same as @response, just without a status
327
+ @requestBody <Model> // Expects model specification
328
+ @requestBody <myCustomValidator> // Expects validator specification
329
+ @requestBody <Model>.with(relations) // Expects model and its relations
330
+ @requestBody <Model[]>.append("some":"valid json") // append additional properties to a Model
331
+ @requestBody {"foo": "bar"} // Expects a specific JSON
332
+ ```
333
+
334
+ ## `@requestFormDataBody` examples
335
+
336
+ ```ts
337
+ // Providing a raw JSON
338
+ @requestFormDataBody {"name":{"type":"string"},"picture":{"type":"string","format":"binary"}} // Expects a valid OpenAPI 3.x JSON
339
+ ```
340
+
341
+ ```ts
342
+ // Providing a Model, and adding additional fields
343
+ @requestFormDataBody <Model> // Expects a valid OpenAPI 3.x JSON
344
+ @requestFormDataBody <Model>.exclude(property1).append("picture":{"type":"string","format":"binary"}) // Expects a valid OpenAPI 3.x JSON
345
+ ```
346
+
347
+ ---
348
+
349
+ # **Practical example**
350
+
351
+ `config/swagger.ts`
352
+
353
+ ```ts
354
+ export default {
355
+ path: __dirname + "../",
356
+ title: "YourProject",
357
+ version: "1.0.0",
358
+ tagIndex: 2,
359
+ ignore: ["/swagger", "/docs", "/v1", "/", "/something/*", "*/something"],
360
+ common: {
361
+ parameters: {
362
+ sortable: [
363
+ {
364
+ in: "query",
365
+ name: "sortBy",
366
+ schema: { type: "string", example: "foo" },
367
+ },
368
+ {
369
+ in: "query",
370
+ name: "sortType",
371
+ schema: { type: "string", example: "ASC" },
372
+ },
373
+ ],
374
+ },
375
+ headers: {
376
+ paginated: {
377
+ "X-Total-Pages": {
378
+ description: "Total amount of pages",
379
+ schema: { type: "integer", example: 5 },
380
+ },
381
+ "X-Total": {
382
+ description: "Total amount of results",
383
+ schema: { type: "integer", example: 100 },
384
+ },
385
+ "X-Per-Page": {
386
+ description: "Results per page",
387
+ schema: { type: "integer", example: 20 },
388
+ },
389
+ },
390
+ },
391
+ },
392
+ };
393
+ ```
394
+
395
+ `app/Controllers/Http/SomeController.ts`
396
+
397
+ ```ts
398
+ export default class SomeController {
399
+ /**
400
+ * @index
401
+ * @operationId getProducts
402
+ * @description Returns array of producs and it's relations
403
+ * @responseBody 200 - <Product[]>.with(relations)
404
+ * @paramUse(sortable, filterable)
405
+ * @responseHeader 200 - @use(paginated)
406
+ * @responseHeader 200 - X-pages - A description of the header - @example(test)
407
+ */
408
+ public async index({ request, response }: HttpContextContract) {}
409
+
410
+ /**
411
+ * @show
412
+ * @paramPath id - Describe the path param - @type(string) @required
413
+ * @paramQuery foo - Describe the query param - @type(string) @required
414
+ * @description Returns a product with it's relation on user and user relations
415
+ * @responseBody 200 - <Product>.with(user, user.relations)
416
+ * @responseBody 404
417
+ */
418
+ public async show({ request, response }: HttpContextContract) {}
419
+
420
+ /**
421
+ * @update
422
+ * @responseBody 200
423
+ * @responseBody 404 - Product could not be found
424
+ * @requestBody <Product>
425
+ */
426
+ public async update({ request, response }: HttpContextContract) {}
427
+
428
+ /**
429
+ * @myCustomFunction
430
+ * @summary Lorem ipsum dolor sit amet
431
+ * @paramPath provider - The login provider to be used - @enum(google, facebook, apple)
432
+ * @responseBody 200 - {"token": "xxxxxxx"}
433
+ * @requestBody {"code": "xxxxxx"}
434
+ */
435
+ public async myCustomFunction({ request, response }: HttpContextContract) {}
436
+ }
437
+ ```
438
+
439
+ ---
440
+
441
+ ## What does it do?
442
+
443
+ AutoSwagger tries to extracat as much information as possible to generate swagger-docs for you.
444
+
445
+ ## Paths
446
+
447
+ Automatically generates swagger path-descriptions, based on your application routes. It also detects endpoints, protected by the auth-middlware.
448
+
449
+ ![paths](https://i.imgur.com/EnPw6xT.png)
450
+
451
+ ### Responses and RequestBody
452
+
453
+ Generates responses and requestBody based on your simple Controller-Annotation (see Examples)
454
+
455
+ ---
456
+
457
+ ## Schemas
458
+
459
+ ### Models
460
+
461
+ Automatically generates swagger schema-descriptions based on your models
462
+
463
+ ![alt](https://i.imgur.com/FEdLplp.png)
464
+
465
+ ### Interfaces
466
+
467
+ Instead of using `param: any` you can now use custom interfaces `param: UserDetails`. The interfaces files need to be located at `app/Interfaces/`
468
+
469
+ ### Enums
470
+
471
+ If you use enums in your models, AutoSwagger will detect them from `app/Types/` folder and add them to the schema.
472
+ If you want to add enum on ExampleValue, you can use `.append(enumFieldExample)`
473
+
474
+ Example:
475
+
476
+ ```ts
477
+ @responseBody 200 - <Model>.with(relations).append(enumFieldExample)
478
+ ```
479
+
480
+ ## Extend Models
481
+
482
+ Add additional documentation to your Models properties.
483
+
484
+ ### SoftDelete
485
+
486
+ Either use `compose(BaseModel, SoftDeletes)` or add a line `@swagger-softdeletes` to your Model.
487
+
488
+ ## Attention
489
+
490
+ The below comments MUST be placed **1 line** above the property.
491
+
492
+ ---
493
+
494
+ **@no-swagger**
495
+ Although, autoswagger detects `serializeAs: null` fields automatically, and does not show them. You can use @no-swagger for other fields.
496
+
497
+ **@enum(foo, bar)**
498
+ If a field has defined values, you can add them into an enum. This is usesfull for something like a status field.
499
+
500
+ **@format(string)**
501
+ Specify a format for that field, i.e. uuid, email, binary, etc...
502
+
503
+ **@example(foo bar)**
504
+ Use this field to provide own example values for specific fields
505
+
506
+ **@props({"minLength": 10, "foo": "bar"})**
507
+ Use this field to provide additional properties to a field, like minLength, maxLength, etc. Needs to bee valid JSON.
508
+
509
+ **@required**
510
+ Specify that the field is required
511
+
512
+ ```ts
513
+ // SomeModel.js
514
+ @hasMany(() => ProductView)
515
+ // @no-swagger
516
+ public views: HasMany<typeof ProductView>
517
+
518
+
519
+ @column()
520
+ // @enum(pending, active, deleted)
521
+ public status: string
522
+
523
+ @column()
524
+ // @example(johndoe@example.com)
525
+ public email: string
526
+
527
+ @column()
528
+ // @props({"minLength": 10})
529
+ public age: number
530
+
531
+ ```
532
+
533
+ ---
534
+
535
+ ## Production environment
536
+
537
+ > [!WARNING]
538
+ > Make sure **NODE_ENV=production** in your production environment or whatever you set in `options.productionEnv`
539
+
540
+ To make it work in production environments, additional steps are required
541
+
542
+ - Create a new command for `docs:generate` [See official documentation](https://docs.adonisjs.com/guides/ace/creating-commands)
543
+
544
+ - This should create a new file in `commands/DocsGenerate.ts`
545
+
546
+ - Use the provided [`DocsGenerate.ts.examle`](https://github.com/necessarylion/adonis-autoswagger/blob/main/DocsGenerate.ts.example)/[`DocsGeneratev6.ts.example`](https://github.com/necessarylion/adonis-autoswagger/blob/main/DocsGeneratev6.ts.example) and put its contents into your newly created `DocsGenerate.ts`
547
+
548
+ - Modify `/start/env.ts` as follows
549
+
550
+ ```ts
551
+ //...
552
+ // this is necessary to make sure that the `DocsGenerate` command will run in CI/CD pipelines without setting environment variables
553
+ const isNodeAce = process.argv.some(
554
+ (arg) => arg.endsWith("/ace") || arg === "ace"
555
+ );
556
+
557
+ export default await Env.create(
558
+ new URL("../", import.meta.url),
559
+ isNodeAce
560
+ ? {}
561
+ : {
562
+ // leave other settings as is
563
+ NODE_ENV: Env.schema.enum([
564
+ "development",
565
+ "production",
566
+ "test",
567
+ ] as const),
568
+ PORT: Env.schema.number(),
569
+ }
570
+ );
571
+
572
+ //...
573
+ ```
574
+
575
+ - Execute the following
576
+
577
+ ```bash
578
+ node ace docs:generate
579
+ node ace build --production
580
+ cp swagger.yml build/
581
+ ```
582
+
583
+ ## Known Issues
584
+
585
+ - Interfaces with objects are not working like `interface Test {foo: {bar: string}}`
586
+ - Solution, just extract the object as it's own interface
@@ -0,0 +1,6 @@
1
+ export declare function serializeV6Middleware(mw: any): string[];
2
+ export declare function serializeV6Handler(handler: any): Promise<any>;
3
+ export declare function parseBindingReference(binding: string | [any | any, any]): Promise<{
4
+ moduleNameOrPath: string;
5
+ method: string;
6
+ }>;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serializeV6Middleware = serializeV6Middleware;
4
+ exports.serializeV6Handler = serializeV6Handler;
5
+ exports.parseBindingReference = parseBindingReference;
6
+ function serializeV6Middleware(mw) {
7
+ return [...mw.all()].reduce((result, one) => {
8
+ if (typeof one === "function") {
9
+ result.push(one.name || "closure");
10
+ return result;
11
+ }
12
+ if ("name" in one && one.name) {
13
+ result.push(one.name);
14
+ }
15
+ return result;
16
+ }, []);
17
+ }
18
+ async function serializeV6Handler(handler) {
19
+ /**
20
+ * Value is a controller reference
21
+ */
22
+ if ("reference" in handler) {
23
+ return {
24
+ type: "controller",
25
+ ...(await parseBindingReference(handler.reference)),
26
+ };
27
+ }
28
+ /**
29
+ * Value is an inline closure
30
+ */
31
+ return {
32
+ type: "closure",
33
+ name: handler.name || "closure",
34
+ };
35
+ }
36
+ async function parseBindingReference(binding) {
37
+ const parseImports = (await import("parse-imports")).default;
38
+ /**
39
+ * The binding reference is a magic string. It might not have method
40
+ * name attached to it. Therefore we split the string and attempt
41
+ * to find the method or use the default method name "handle".
42
+ */
43
+ if (typeof binding === "string") {
44
+ const tokens = binding.split(".");
45
+ if (tokens.length === 1) {
46
+ return { moduleNameOrPath: binding, method: "handle" };
47
+ }
48
+ return { method: tokens.pop(), moduleNameOrPath: tokens.join(".") };
49
+ }
50
+ const [bindingReference, method] = binding;
51
+ /**
52
+ * Parsing the binding reference for dynamic imports and using its
53
+ * import value.
54
+ */
55
+ const imports = [...(await parseImports(bindingReference.toString()))];
56
+ const importedModule = imports.find(($import) => $import.isDynamicImport && $import.moduleSpecifier.value);
57
+ if (importedModule) {
58
+ return {
59
+ moduleNameOrPath: importedModule.moduleSpecifier.value,
60
+ method: method || "handle",
61
+ };
62
+ }
63
+ /**
64
+ * Otherwise using the name of the binding reference.
65
+ */
66
+ return {
67
+ moduleNameOrPath: bindingReference.name,
68
+ method: method || "handle",
69
+ };
70
+ }