{"id":15584088,"url":"https://github.com/jmcdo29/nestjs-spelunker","last_synced_at":"2025-05-16T08:04:52.639Z","repository":{"id":42124882,"uuid":"239954423","full_name":"jmcdo29/nestjs-spelunker","owner":"jmcdo29","description":"A NestJS Module for generating a NestJS Applications Module Dependency Graph.","archived":false,"fork":false,"pushed_at":"2025-01-26T16:34:54.000Z","size":866,"stargazers_count":348,"open_issues_count":9,"forks_count":19,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-08T20:18:09.010Z","etag":null,"topics":["graph","hacktoberfest","nestjs","utility"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jmcdo29.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-02-12T07:46:07.000Z","updated_at":"2025-04-08T14:40:47.000Z","dependencies_parsed_at":"2024-10-31T02:04:35.612Z","dependency_job_id":"a5caddcd-720a-4b2c-90a9-4ee300b10868","html_url":"https://github.com/jmcdo29/nestjs-spelunker","commit_stats":{"total_commits":96,"total_committers":7,"mean_commits":"13.714285714285714","dds":0.53125,"last_synced_commit":"3cf07cbaef306e885eb031ad46f979cc7497dfd1"},"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmcdo29%2Fnestjs-spelunker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmcdo29%2Fnestjs-spelunker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmcdo29%2Fnestjs-spelunker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmcdo29%2Fnestjs-spelunker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jmcdo29","download_url":"https://codeload.github.com/jmcdo29/nestjs-spelunker/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254493378,"owners_count":22080126,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["graph","hacktoberfest","nestjs","utility"],"created_at":"2024-10-02T20:22:59.110Z","updated_at":"2025-05-16T08:04:47.630Z","avatar_url":"https://github.com/jmcdo29.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# NestJS-Spelunker\n\n## Description\n\nThis module does a bit of a dive through the provided module and reads through the dependency tree from the point of entry given. It will find what a module `imports`, `provides`, has `controllers` for, and `exports` and will recursively search through the dependency tree until all modules have been scanned. For `providers` if there is a custom provider, the Spelunker will do its best to determine if Nest is to use a value, a class/standard, or a factory, and if a factory, what value is to be injected.\n\n## Installation\n\nPretty straightforward installation:\n\n```sh\nnpm i nestjs-spelunker\nyarn add nestjs-spelunker\npnpm i nestjs-spelunker\n```\n\n## Exploration Mode\n\n### Exploration Usage\n\nMuch like the [`SwaggerModule`](https://github.com/nestjs/swagger), the `SpelunkerModule` is not a module that you register within Nest's DI system, but rather use after the DI system has done all of the heavy lifting. Simple usage of the Spelunker could be like:\n\n```ts\n// ...\nimport { SpelunkerModule } from 'nestjs-spelunker'; \n\nasync function bootstrap() {\n  const app = await NestFactory.create(AppModule);\n  // const app = await NestFactory.createApplicationContext(AppModule);\n  console.log(SpelunkerModule.explore(app));\n  // ...\n}\n// ...\n```\n\nThe `SpelunkerModule` will not get in the way of application bootstrapping, and will still allow for the server to listen.\n\n#### Excluding modules\n\n```ts\nSpelunkerModule.explore(app, {\n  // A list of regexes or predicate functions to apply over modules that will be ignored\n  ignoreImports: [\n    /^TypeOrmModule/i,\n    (moduleName) =\u003e moduleName.endsWith('something'),\n  ],\n})\n```\n\n### Exploration Sample Output\n\nGiven the following source code\n\n\u003cdetails\u003e\n\u003csummary\u003eSample code\u003c/summary\u003e\n\n```ts\n// main.ts\nimport * as util from 'util'\nimport { NestFactory } from '@nestjs/core'\nimport { SpelunkerModule } from 'nestjs-spelunker'\nimport { AppModule } from './app.module'\n\nasync function bootstrap() {\n  const app = await NestFactory.createApplicationContext(AppModule, { logger: false })\n  console.log(\n    util.inspect( SpelunkerModule.explore(app), { depth: Infinity, colors: true } )\n  )\n}\nbootstrap();\n\n// src/app.module.ts\nimport { Module, Injectable, Controller } from '@nestjs/common'\n\n@Controller('hamsters')\nexport class HamstersController {}\n@Injectable()\nexport class HamstersService {}\n\n@Module({\n  controllers: [HamstersController],\n  providers: [HamstersService],\n})\nexport class HamstersModule {}\n\n\n@Controller('dogs')\nexport class DogsController {}\nexport class DogsService {}\n\n@Module({\n  controllers: [DogsController],\n  providers: [\n    {\n      provide: DogsService,\n      inject: ['someString'],\n      useFactory: (str: string) =\u003e new DogsService(),\n    },\n    {\n      provide: 'someString',\n      useValue: 'my string',\n    },\n  ],\n  exports: [DogsService],\n})\nexport class DogsModule {}\n\n\n@Controller('cats')\nexport class CatsController {}\n@Injectable()\nexport class CatsService {}\n\n@Module({\n  controllers: [CatsController],\n  providers: [CatsService],\n})\nexport class CatsModule {}\n\n\nexport class AnimalsService {}\n@Controller('animals')\nexport class AnimalsController {}\n\n@Module({\n  imports: [CatsModule, DogsModule, HamstersModule],\n  controllers: [AnimalsController],\n  providers: [\n    {\n      provide: AnimalsService,\n      useValue: new AnimalsService(),\n    }\n  ],\n  exports: [DogsModule],\n})\nexport class AnimalsModule {}\n\n\n@Module({\n  imports: [AnimalsModule],\n})\nexport class AppModule {}\n```\n\n\u003c/details\u003e\n\nit outputs this:\n\n```js\n[\n  {\n    name: 'AppModule',\n    imports: [ 'AnimalsModule' ],\n    providers: {},\n    controllers: [],\n    exports: []\n  },\n  {\n    name: 'AnimalsModule',\n    imports: [ 'CatsModule', 'DogsModule', 'HamstersModule' ],\n    providers: { AnimalsService: { method: 'value' } },\n    controllers: [ 'AnimalsController' ],\n    exports: [ 'DogsModule' ]\n  },\n  {\n    name: 'CatsModule',\n    imports: [],\n    providers: { CatsService: { method: 'standard' } },\n    controllers: [ 'CatsController' ],\n    exports: []\n  },\n  {\n    name: 'DogsModule',\n    imports: [],\n    providers: {\n      DogsService: { method: 'factory', injections: [ 'someString' ] },\n      someString: { method: 'value' }\n    },\n    controllers: [ 'DogsController' ],\n    exports: [ 'DogsService' ]\n  },\n  {\n    name: 'HamstersModule',\n    imports: [],\n    providers: { HamstersService: { method: 'standard' } },\n    controllers: [ 'HamstersController' ],\n    exports: []\n  }\n]\n```\n\nIn this example, `AppModule` imports `AnimalsModule`, and `AnimalsModule` imports `CatsModule`, `DogsModule`, and `HamstersModule` and each of those has its own set of `providers` and `controllers`.\n\n## Graph Mode\n\nSometimes you want to visualize the module inter-dependencies so you can better reason about them. The `SpelunkerModule` has a `graph` method that builds on the output of the `explore` method by generating a doubly-linked graph where each node represents a module and each edge a link to that module's dependencies or dependents. The `getEdges` method can traverse this graph from from the root (or any given) node, recursively following dependencies and returning a flat array of edges. These edges can be easily mapped to inputs for graphing tools, such as [Mermaid](https://mermaid-js.github.io/mermaid/#/).\n\n### Graphing Usage\n\nAssume you have the sample output of the above `explore` section in a variable called tree. The following code will generate the list of edges suitable for pasting into a [Mermaid](https://mermaid-js.github.io/mermaid/#/) graph.\n\n```ts\nconst tree = SpelunkerModule.explore(app);\nconst root = SpelunkerModule.graph(tree);\nconst edges = SpelunkerModule.findGraphEdges(root);\nconsole.log('graph LR');\nconst mermaidEdges = edges.map(\n  ({ from, to }) =\u003e `  ${from.module.name}--\u003e${to.module.name}`,\n);\nconsole.log(mermaidEdges.join('\\n'));\n```\n\n```mermaid\ngraph LR\n  AppModule--\u003eAnimalsModule\n  AnimalsModule--\u003eCatsModule\n  AnimalsModule--\u003eDogsModule\n  AnimalsModule--\u003eHamstersModule\n```\n\nThe edges can certainly be transformed into formats more suitable for other visualization tools. And the graph can be traversed with other strategies.\n\n## Debug Mode\n\nEvery now again again you may find yourself running into problems where Nest can't resolve a provider's dependencies. The `SpelunkerModule` has a `debug` method that's meant to help out with this kind of situation.\n\n### Debug Usage\n\nAssume you have a `DogsModule` with the following information:\n\n```ts\n@Module({\n  controller: [DogsController],\n  exports: [DogsService],\n  providers: [\n    {\n      provide: 'someString',\n      useValue: 'something',\n    },\n    {\n      provide: DogsService,\n      inject: ['someString'],\n      useFactory: (someStringInjection: string) =\u003e {\n        return new DogsService(someStringInjection),\n      },\n    }\n  ]\n})\nexport class DogsModule {}\n```\n\nNow the `SpelunkerModule.debug()` method can be used anywhere with the `DogsModule` to get the dependency tree of the `DogsModule` including what the controller depends on, what imports are made, and what providers exist and their token dependencies.\n\n```ts\nasync function bootstrap() {\n  const dogsDeps = await SpelunkerModule.debug(DogsModule);\n  const app = await NestFactory.create(AppModule);\n  await app.listen(3000);\n}\n```\n\nBecause this method does not require the `INestApplicationContext` it can be used _before_ the `NestFactory` allowing you to have insight into what is being seen as the injection values and what's needed for the module to run.\n\n### Debug Sample Output\n\nThe output of the `debug()` method is an array of metadata, imports, controllers, exports, and providers. The `DogsModule` from above would look like this:\n\n```js\n[\n  {\n    name: 'DogsModule',\n    imports: [],\n    providers: [\n      {\n        name: 'someString',\n        dependencies: [],\n        type: 'value',\n      },\n      {\n        name: 'DogsService',\n        dependencies: ['someString'],\n        type: 'factory',\n      },\n    ],\n    controllers: [\n      {\n        name: 'DogsController',\n        dependencies: ['DogsService'],\n      },\n    ],\n    exports: [\n      {\n        name: 'DogsService',\n        type: 'provider',\n      },\n    ],\n  },\n];\n```\n\n### Debug Messages\n\nIf you are using the `debug` method and happen to have an invalid circular, the `SpelunkerModule` will write message to the log about the possibility of an unmarked circular dependency, meaning a missing `forwardRef` and the output will have `*****` in place of the `imports` where there's a problem reading the imported module.\n\n## Caution\n\nThis package is in early development, and any bugs found or improvements that can be thought of would be amazingly helpful. You can [log a bug here](../../issues/new), and you can reach out to me on Discord at [PerfectOrphan#6003](https://discordapp.com).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjmcdo29%2Fnestjs-spelunker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjmcdo29%2Fnestjs-spelunker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjmcdo29%2Fnestjs-spelunker/lists"}