[
  {
    "path": ".dockerignore",
    "content": "/node_modules/\ndist\n\n# Git / VCS\n.git/\n\n# Editor / IDE\n.vscode/\n\n# Environment / Secrets\n.env\n\n# Logs\n*.log\nnpm-debug.log*\n\n# OS generated files\n.DS_Store\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "# Default owner for all files\n* @bagelbits\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [5e-bits]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: bagelbits\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "## What does this do?\n\n\\<It's not clear if I don't update this text with relevant info\\>\n\n## How was it tested?\n\n\\<It's not clear if I don't update this text with relevant info\\>\n\n## Is there a Github issue this is resolving?\n\n\\<It's not clear if I don't update this text with relevant info\\>\n\n## Was any impacted documentation updated to reflect this change?\n\n\\<It's not clear if I don't update this text with relevant info\\>\n\n## Here's a fun image for your troubles\n\n\\<Add a fun image here\\>\n"
  },
  {
    "path": ".github/actions/validate-pr-title/action.yml",
    "content": "name: 'Validate PR Title'\ndescription: 'Validates that PR titles follow conventional commit format with custom types'\ninputs:\n  title:\n    description: 'PR title to validate'\n    required: true\noutputs:\n  valid:\n    description: 'Whether the title is valid'\n    value: ${{ steps.validate.outputs.valid }}\nruns:\n  using: 'composite'\n  steps:\n    - name: Validate PR title\n      id: validate\n      shell: bash\n      run: |\n        title=\"${{ inputs.title }}\"\n\n        # Define allowed types\n        allowed_types=\"feat|fix|docs|style|refactor|perf|test|build|ci|chore|deps\"\n\n        # Check if title matches conventional commit format\n        if echo \"$title\" | grep -qE \"^($allowed_types)(\\(.+\\))?!?: .+\"; then\n          echo \"valid=true\" >> $GITHUB_OUTPUT\n          echo \"✅ PR title is valid: $title\"\n        else\n          echo \"valid=false\" >> $GITHUB_OUTPUT\n          echo \"❌ PR title is invalid: $title\"\n          echo \"\"\n          echo \"Expected format: <type>[optional scope]: <description>\"\n          echo \"\"\n          echo \"Allowed types: $allowed_types\"\n          echo \"\"\n          echo \"Examples:\"\n          echo \"  feat: add new feature\"\n          echo \"  fix: resolve bug\"\n          echo \"  deps: update dependencies\"\n          echo \"  chore: update CI configuration\"\n          exit 1\n        fi\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file\n\nversion: 2\nupdates:\n  - package-ecosystem: 'npm' # See documentation for possible values\n    directory: '/' # Location of package manifests\n    schedule:\n      interval: 'weekly'\n    commit-message:\n      prefix: 'chore'\n      include: 'scope'\n  - package-ecosystem: 'github-actions'\n    # Workflow files stored in the default location of `.github/workflows`. (You don't need to specify `/.github/workflows` for `directory`. You can use `directory: \"/\"`.)\n    directory: '/'\n    schedule:\n      interval: 'weekly'\n    commit-message:\n      prefix: 'chore'\n      include: 'scope'\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node\n# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions\n\nname: CI\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\nenv:\n  REGISTRY: ghcr.io\n\njobs:\n  lint:\n    name: Run linter\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # @v6.0.0\n      - name: Use Node.js 22.x\n        uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # @v6.0.0\n        with:\n          node-version: 22.x\n      - run: npm install\n      - name: Lint Code\n        run: npm run lint\n      - name: Validate OpenAPI Spec\n        run: npm run validate-swagger\n\n  unit:\n    name: Run tests\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # @v6.0.0\n      - name: Use Node.js 22.x\n        uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # @v6.0.0\n        with:\n          node-version: 22.x\n      - run: npm install\n      - run: npm run test:unit\n\n  integration:\n    name: Run Integration tests\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # @v6.0.0\n      - run: npm run test:integration:local\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: 'CodeQL'\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [main]\n  schedule:\n    - cron: '15 6 * * 5'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: ['javascript']\n        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]\n        # Learn more:\n        # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6\n\n      # Initializes the CodeQL tools for scanning.\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v4\n        with:\n          languages: ${{ matrix.language }}\n          # If you wish to specify custom queries, you can do so here or in a config file.\n          # By default, queries listed here will override any specified in a config file.\n          # Prefix the list here with \"+\" to use these queries and those in the config file.\n          # queries: ./path/to/local/query, your-org/your-repo/queries@main\n\n      # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).\n      # If this step fails, then you should remove it and run the build manually (see below)\n      - name: Autobuild\n        uses: github/codeql-action/autobuild@v4\n\n      # ℹ️ Command-line programs to run using the OS shell.\n      # 📚 https://git.io/JvXDl\n\n      # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines\n      #    and modify them (or add more) to build your code if your project\n      #    uses a compiled language\n\n      #- run: |\n      #   make bootstrap\n      #   make release\n\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v4\n"
  },
  {
    "path": ".github/workflows/lint-pr.yml",
    "content": "name: \"Lint PR\"\n\non:\n  pull_request_target:\n    types:\n      - opened\n      - edited\n      - synchronize\n\njobs:\n  main:\n    name: Validate PR title\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Validate PR title\n        uses: ./.github/actions/validate-pr-title\n        with:\n          title: ${{ github.event.pull_request.title }}\n"
  },
  {
    "path": ".github/workflows/release-please.yml",
    "content": "name: Release Please\n\non:\n  push:\n    branches: [main]\n  workflow_dispatch:\n\npermissions:\n  contents: write\n  pull-requests: write\n\njobs:\n  release-please:\n    runs-on: ubuntu-latest\n    outputs:\n      release_created: ${{ steps.release.outputs.release_created }}\n      version: ${{ steps.release.outputs.version }}\n    steps:\n      - name: Generate Deploy Bot token\n        id: generate-token\n        uses: actions/create-github-app-token@v3\n        with:\n          app-id: ${{ secrets.DEPLOYMENT_APP_ID }}\n          private-key: ${{ secrets.DEPLOYMENT_APP_PRIVATE_KEY }}\n      - name: Checkout\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - name: Release Please\n        id: release\n        uses: googleapis/release-please-action@v4\n        with:\n          token: ${{ steps.generate-token.outputs.token }}\n          release-type: node\n          config-file: ./release-please-config.json\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  release:\n    types: [published]\n  workflow_dispatch:\n  repository_dispatch:\n\nenv:\n  REGISTRY: ghcr.io\n\njobs:\n  build-and-publish:\n    name: Build and Publish Release Assets\n    runs-on: ubuntu-latest\n    if: github.repository == '5e-bits/5e-srd-api'\n    permissions:\n      contents: write\n      packages: write\n    steps:\n      - name: Determine Release Tag\n        id: tag\n        run: |\n          if [ \"${{ github.event_name }}\" = \"release\" ]; then\n            echo \"tag=${{ github.event.release.tag_name }}\" >> $GITHUB_OUTPUT\n          else\n            # For workflow_dispatch, get the latest release tag\n            LATEST_TAG=$(gh api repos/${{ github.repository }}/releases/latest --jq .tag_name)\n            echo \"tag=$LATEST_TAG\" >> $GITHUB_OUTPUT\n          fi\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Checkout\n        uses: actions/checkout@v6\n        with:\n          ref: ${{ steps.tag.outputs.tag }}\n      - name: Use Node.js 22.x\n        uses: actions/setup-node@v6\n        with:\n          node-version: 22.x\n      - name: Install Dependencies\n        run: npm ci\n      - name: Build Artifacts\n        run: |\n          npm run bundle-swagger\n          npm run gen-postman\n      - name: Upload Release Assets\n        if: github.event_name == 'release'\n        run: |\n          gh release upload ${{ github.event.release.tag_name }} \\\n            ./dist/openapi.yml \\\n            ./dist/openapi.json \\\n            ./dist/collection.postman.json\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n  container-release:\n    name: Container Release\n    runs-on: ubuntu-latest\n    if: github.repository == '5e-bits/5e-srd-api'\n    env:\n      IMAGE_NAME: ${{ github.repository }}\n    permissions:\n      contents: read\n      packages: write\n    steps:\n      - name: Determine Release Tag\n        id: tag\n        run: |\n          if [ \"${{ github.event_name }}\" = \"release\" ]; then\n            echo \"tag=${{ github.event.release.tag_name }}\" >> $GITHUB_OUTPUT\n          else\n            # For workflow_dispatch, get the latest release tag\n            LATEST_TAG=$(gh api repos/${{ github.repository }}/releases/latest --jq .tag_name)\n            echo \"tag=$LATEST_TAG\" >> $GITHUB_OUTPUT\n          fi\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Checkout repository\n        uses: actions/checkout@v6\n        with:\n          ref: ${{ steps.tag.outputs.tag }}\n      - name: Log in to the Container registry\n        uses: docker/login-action@v4\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n      - name: Build and push Docker image\n        uses: docker/build-push-action@v7\n        with:\n          context: .\n          push: true\n          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }},${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest\n          labels: version=${{ steps.tag.outputs.tag }}\n\n  heroku-deploy:\n    name: Deploy to Heroku\n    runs-on: ubuntu-latest\n    if: github.repository == '5e-bits/5e-srd-api'\n    needs: [build-and-publish, container-release]\n    steps:\n      - name: Determine Release Tag\n        id: tag\n        run: |\n          if [ \"${{ github.event_name }}\" = \"release\" ]; then\n            echo \"tag=${{ github.event.release.tag_name }}\" >> $GITHUB_OUTPUT\n          else\n            # For workflow_dispatch, get the latest release tag\n            LATEST_TAG=$(gh api repos/${{ github.repository }}/releases/latest --jq .tag_name)\n            echo \"tag=$LATEST_TAG\" >> $GITHUB_OUTPUT\n          fi\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Checkout\n        uses: actions/checkout@v6\n        with:\n          ref: ${{ steps.tag.outputs.tag }}\n      - name: Install Heroku CLI\n        run: curl https://cli-assets.heroku.com/install.sh | sh\n      - name: Deploy to Heroku\n        uses: akhileshns/heroku-deploy@v3.14.15\n        with:\n          heroku_api_key: ${{ secrets.HEROKU_API_KEY }}\n          heroku_app_name: \"dnd-5e-srd-api\"\n          heroku_email: \"cdurianward@gmail.com\"\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules\njspm_packages\n\n# Optional npm cache directory\n.npm\n\n# Optional REPL history\n.node_repl_history\n.env\n\n# JetBrains config files\n.idea\n\ndist\ndist/**\n\n# Portman creates some temporary files here during conversion.\ntmp/**\ncollection.postman.json\n\n# Docker\ndocker-compose.override.yml\n\n# Claude\n.claude\n"
  },
  {
    "path": ".nvmrc",
    "content": "lts/jod\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"printWidth\": 100,\n  \"singleQuote\": true,\n  \"trailingComma\": \"none\",\n  \"tabWidth\": 2,\n  \"semi\": false,\n  \"quoteProps\": \"as-needed\"\n}\n"
  },
  {
    "path": ".redocly.yaml",
    "content": "extends:\n  - recommended\n\nrules:\n  operation-operationId: off\n  operation-4xx-response: off\n  # no-invalid-media-type-examples: off\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# [5.1.0](https://github.com/5e-bits/5e-srd-api/compare/v5.0.0...v5.1.0) (2025-09-15)\n\n\n### Features\n\n* **2024:** Add equipment and categories ([#815](https://github.com/5e-bits/5e-srd-api/issues/815)) ([81dae46](https://github.com/5e-bits/5e-srd-api/commit/81dae461faa031b93d9c0edf86a458f7c1f3f2c6))\n\n# [5.0.0](https://github.com/5e-bits/5e-srd-api/compare/v4.2.1...v5.0.0) (2025-09-04)\n\n\n* refactor(race/subrace)!: remove redundant data ([#825](https://github.com/5e-bits/5e-srd-api/issues/825)) ([043fc16](https://github.com/5e-bits/5e-srd-api/commit/043fc160c5f6d41d88a18d8cac11f66f4e2a55d9))\n\n\n### BREAKING CHANGES\n\n* dropped the `race.starting_proficiencies`,\n`race.starting_proficiency_options`, `subrace.starting_proficiencies`,\n`subrace.language_options`, and `subrace.languages` properties of all\nraces and subraces in the database. Clients can instead find this data\non the corresponding traits linked to each race or subrace.\n\n## How was it tested?\n\nI ran the database + API project locally with Docker and called the\nendpoints of the various classes and subclasses. I also ran the unit and\nintegration tests in the API project.\n\n## Is there a Github issue this is resolving?\n\nhttps://github.com/5e-bits/5e-database/issues/874\n\n## Was any impacted documentation updated to reflect this change?\n\nI touched every reference of the properties in the API project. I took a\nlook at the docs project, but couldn't fully find my way around the\nproject to give a clear indication on if anything needed to change.\n\n## Here's a fun image for your troubles\nMy players once sold an iron pot to well known business woman and secret\nmember of the Zhentarim and convinced her that it was a magic pot that\ncan restore spoiled food. They even sneaked into her house to cast\npurify food and drink on it to make sure she believed them.\n![Iron\nPot](https://github.com/user-attachments/assets/506e3b32-4093-42fd-8fa0-f8fd95bb85cb)\n\n## [5.4.0](https://github.com/5e-bits/5e-srd-api/compare/v5.3.0...v5.4.0) (2026-04-01)\n\n\n### Features\n\n* **2024:** Add magic items ([#1044](https://github.com/5e-bits/5e-srd-api/issues/1044)) ([8686fd0](https://github.com/5e-bits/5e-srd-api/commit/8686fd0ca89358cd3a256353985ec73fd9ab5d4f))\n* **2024:** Add Species, Subspecies, and Traits ([#1038](https://github.com/5e-bits/5e-srd-api/issues/1038)) ([0bcde32](https://github.com/5e-bits/5e-srd-api/commit/0bcde324e7228b85fd40d324aec0d865287a4872))\n* **2024:** Add subclasses ([#1043](https://github.com/5e-bits/5e-srd-api/issues/1043)) ([29ae4fa](https://github.com/5e-bits/5e-srd-api/commit/29ae4fa8f4efae0eb29bc9d6523e5c48839fa0c7))\n\n## [5.3.0](https://github.com/5e-bits/5e-srd-api/compare/v5.2.4...v5.3.0) (2026-03-10)\n\n\n### Features\n\n* **2024:** Add Backgrounds, Feats, and Proficiencies for 2024 ([#1018](https://github.com/5e-bits/5e-srd-api/issues/1018)) ([b584a84](https://github.com/5e-bits/5e-srd-api/commit/b584a8428ab079b7e889b54f28eeb33a0347cea5))\n\n## [5.2.4](https://github.com/5e-bits/5e-srd-api/compare/v5.2.3...v5.2.4) (2025-12-11)\n\n\n### Bug Fixes\n\n* **ci:** Use app token ([#956](https://github.com/5e-bits/5e-srd-api/issues/956)) ([5abffd8](https://github.com/5e-bits/5e-srd-api/commit/5abffd830dd00dbd5bb55dd96c1627d002fed95d))\n\n## [5.2.3](https://github.com/5e-bits/5e-srd-api/compare/v5.2.2...v5.2.3) (2025-12-11)\n\n\n### Bug Fixes\n\n* **ci:** Release is now automatic on release PR merge ([#954](https://github.com/5e-bits/5e-srd-api/issues/954)) ([d8a798d](https://github.com/5e-bits/5e-srd-api/commit/d8a798d3bb3552c6ba5a3248cda80923013ac6b9))\n\n## [5.2.2](https://github.com/5e-bits/5e-srd-api/compare/v5.2.1...v5.2.2) (2025-12-04)\n\n\n### Bug Fixes\n\n* Add Spell field resolver to Subclass Resolver ([#952](https://github.com/5e-bits/5e-srd-api/issues/952)) ([c15ef5d](https://github.com/5e-bits/5e-srd-api/commit/c15ef5da7fa3271cd25d67908d7f16d064930113))\n\n## [5.2.1](https://github.com/5e-bits/5e-srd-api/compare/v5.2.0...v5.2.1) (2025-12-01)\n\n\n### Bug Fixes\n\n* missing subclass features ([#946](https://github.com/5e-bits/5e-srd-api/issues/946)) ([8170142](https://github.com/5e-bits/5e-srd-api/commit/8170142dfd9cea306773d1942e21aab52d5901d6))\n\n## [5.2.0](https://github.com/5e-bits/5e-srd-api/compare/v5.1.0...v5.2.0) (2025-10-24)\n\n\n### Features\n\n* **release:** Use release please ([#911](https://github.com/5e-bits/5e-srd-api/issues/911)) ([a8b50dd](https://github.com/5e-bits/5e-srd-api/commit/a8b50dd9256ecf0e5be8517625be839be6e6976e))\n\n\n### Bug Fixes\n\n* **dependabot:** use build instead of deps ([#920](https://github.com/5e-bits/5e-srd-api/issues/920)) ([52a439d](https://github.com/5e-bits/5e-srd-api/commit/52a439da3855b62291007ff37bfd99650e37c7cb))\n\n## [4.2.1](https://github.com/5e-bits/5e-srd-api/compare/v4.2.0...v4.2.1) (2025-06-23)\n\n\n### Bug Fixes\n\n* **races:** Language options is optional for graphql ([#807](https://github.com/5e-bits/5e-srd-api/issues/807)) ([2715e0f](https://github.com/5e-bits/5e-srd-api/commit/2715e0f457cc1404b870e4284b53e2ca227a8355))\n\n# [4.2.0](https://github.com/5e-bits/5e-srd-api/compare/v4.1.1...v4.2.0) (2025-06-13)\n\n\n### Features\n\n* **2024:** Add a bunch of easy endpoints to 2024 ([#800](https://github.com/5e-bits/5e-srd-api/issues/800)) ([2b4871d](https://github.com/5e-bits/5e-srd-api/commit/2b4871da07bea5f8e9d9e907e37e8c4124254cea))\n\n## [4.1.1](https://github.com/5e-bits/5e-srd-api/compare/v4.1.0...v4.1.1) (2025-06-11)\n\n\n### Bug Fixes\n\n* **graphql:** Spell DC now resolves to Ability Score ([#798](https://github.com/5e-bits/5e-srd-api/issues/798)) ([ddb5c26](https://github.com/5e-bits/5e-srd-api/commit/ddb5c26e7dc1794534bd364bcfa20386793dd35d))\n\n# [4.1.0](https://github.com/5e-bits/5e-srd-api/compare/v4.0.3...v4.1.0) (2025-06-11)\n\n\n### Features\n\n* **graphql:** Sets up /graphql/2024 endpoint and abstracts shared code ([#790](https://github.com/5e-bits/5e-srd-api/issues/790)) ([acf7780](https://github.com/5e-bits/5e-srd-api/commit/acf7780e301cf1fb2a166eeefd177a8f4225af3b))\n\n## [4.0.3](https://github.com/5e-bits/5e-srd-api/compare/v4.0.2...v4.0.3) (2025-06-05)\n\n\n### Bug Fixes\n\n* **graphql:** Fix the endpoint for deprecated graphql endpoint ([#791](https://github.com/5e-bits/5e-srd-api/issues/791)) ([2139389](https://github.com/5e-bits/5e-srd-api/commit/2139389d91bfb264acb1929e36a3ebd9ea5b19c2))\n\n## [4.0.2](https://github.com/5e-bits/5e-srd-api/compare/v4.0.1...v4.0.2) (2025-06-02)\n\n\n### Bug Fixes\n\n* **prettier:** Run prettier against everything ([#780](https://github.com/5e-bits/5e-srd-api/issues/780)) ([01905b2](https://github.com/5e-bits/5e-srd-api/commit/01905b2be462990966e7790b2897aebb1cbe578a))\n\n## [4.0.1](https://github.com/5e-bits/5e-srd-api/compare/v4.0.0...v4.0.1) (2025-06-02)\n\n\n### Bug Fixes\n\n* **lint:** Fix simple linting rules ([#778](https://github.com/5e-bits/5e-srd-api/issues/778)) ([3c6cc95](https://github.com/5e-bits/5e-srd-api/commit/3c6cc95753f3f485a58f2e2dfe93cebd1de614d2))\n\n# [4.0.0](https://github.com/5e-bits/5e-srd-api/compare/v3.24.0...v4.0.0) (2025-06-01)\n\n\n* feat(graphql)!: Migrate to typegraphql ([#752](https://github.com/5e-bits/5e-srd-api/issues/752)) ([6bb9e75](https://github.com/5e-bits/5e-srd-api/commit/6bb9e755ea0e35aebe8f2f3bdae0362fa77694e6))\n\n\n### BREAKING CHANGES\n\n* Some fields are now different but more consistent\nacross the graphql endpoints.\n\n## What does this do?\n\n* Completely rewrites our entire GraphQL endpoint using `type-graphql`\nand `zod`\n* Fixes migration errors from the typegoose migration\n\n## How was it tested?\n\nLocally, there are numerous side-by-side comparisons with production.\nBut it is possible I missed something.\n\n## Is there a Github issue this is resolving?\n\nNope. I'm just insane.\n\n## Was any impacted documentation updated to reflect this change?\n\nLuckily, GraphQL introspection is self-documenting.\n\n## Here's a fun image for your troubles\n\n\n![image](https://github.com/user-attachments/assets/22bfa110-aeac-4625-99c6-d57bbc00c4d1)\n\n# [3.24.0](https://github.com/5e-bits/5e-srd-api/compare/v3.23.7...v3.24.0) (2025-05-30)\n\n\n### Features\n\n* **class:** Add `level` query for class spells for filtering spell level ([#776](https://github.com/5e-bits/5e-srd-api/issues/776)) ([0c41457](https://github.com/5e-bits/5e-srd-api/commit/0c414571ea409c37c15d1b82d1da86844cf18ba9))\n\n## [3.23.7](https://github.com/5e-bits/5e-srd-api/compare/v3.23.6...v3.23.7) (2025-05-08)\n\n\n### Bug Fixes\n\n* **deploy:** Undo everything from before ([c3c84d1](https://github.com/5e-bits/5e-srd-api/commit/c3c84d1b20ce930ed1afb558c26955a049ca9560))\n\n## [3.23.6](https://github.com/5e-bits/5e-srd-api/compare/v3.23.5...v3.23.6) (2025-05-08)\n\n\n### Bug Fixes\n\n* **deploy:** Let's try this one more time ([5c5c1cf](https://github.com/5e-bits/5e-srd-api/commit/5c5c1cfa774826837114014e7dbdf9e150e865d3))\n\n## [3.23.5](https://github.com/5e-bits/5e-srd-api/compare/v3.23.4...v3.23.5) (2025-05-08)\n\n\n### Bug Fixes\n\n* **deploy:** Let's try that again. Now using deploy bot as author ([3f5dfa0](https://github.com/5e-bits/5e-srd-api/commit/3f5dfa04c60144c82086829039edb73a8bc9055e))\n\n## [3.23.4](https://github.com/5e-bits/5e-srd-api/compare/v3.23.3...v3.23.4) (2025-05-08)\n\n\n### Bug Fixes\n\n* **deploy:** Use deploy bot for authoring commits ([1f62894](https://github.com/5e-bits/5e-srd-api/commit/1f628949cd218d5939dd8b6f68c702f2355761ba))\n\n## [3.23.3](https://github.com/5e-bits/5e-srd-api/compare/v3.23.2...v3.23.3) (2025-05-06)\n\n\n### Bug Fixes\n\n* **class:** showSpellsForClassAndLevel now gives the spells available at that class level ([#758](https://github.com/5e-bits/5e-srd-api/issues/758)) ([22b1b35](https://github.com/5e-bits/5e-srd-api/commit/22b1b351e7355bcdde04c5fd2fee0c967fb4f2f5))\n\n## [3.23.2](https://github.com/5e-bits/5e-srd-api/compare/v3.23.1...v3.23.2) (2025-05-04)\n\n\n### Bug Fixes\n\n* **desc:** Fix models to match data reality for desc ([#751](https://github.com/5e-bits/5e-srd-api/issues/751)) ([6bcd610](https://github.com/5e-bits/5e-srd-api/commit/6bcd610df4cccc57d79e8ff3b075fee356c34f04))\n\n## [3.23.1](https://github.com/5e-bits/5e-srd-api/compare/v3.23.0...v3.23.1) (2025-05-04)\n\n\n### Bug Fixes\n\n* **images:** Fix key for regex ([6f8a99d](https://github.com/5e-bits/5e-srd-api/commit/6f8a99da050ff4ce0c57bfc5377fcdf57b42f60c))\n\n# [3.23.0](https://github.com/5e-bits/5e-srd-api/compare/v3.22.0...v3.23.0) (2025-05-04)\n\n\n### Features\n\n* **images:** Add /api/images endpoint and use fetch for image fetching ([#750](https://github.com/5e-bits/5e-srd-api/issues/750)) ([fec63b7](https://github.com/5e-bits/5e-srd-api/commit/fec63b78ac075ca8c093896f6bd6c8519ac9870b))\n\n# [3.22.0](https://github.com/5e-bits/5e-srd-api/compare/v3.21.0...v3.22.0) (2025-04-28)\n\n\n### Features\n\n* **node:** Bump to Node 22 ([#742](https://github.com/5e-bits/5e-srd-api/issues/742)) ([8c27177](https://github.com/5e-bits/5e-srd-api/commit/8c271775661473764295d71bc681a31bda6dd01c))\n\n# [3.21.0](https://github.com/5e-bits/5e-srd-api/compare/v3.20.1...v3.21.0) (2025-04-27)\n\n\n### Features\n\n* **release:** Convert semver release to use App instead of PAT ([#736](https://github.com/5e-bits/5e-srd-api/issues/736)) ([aad1a29](https://github.com/5e-bits/5e-srd-api/commit/aad1a29c459bed41daa73af09c4d64db9dcab770))\n\n## [3.20.1](https://github.com/5e-bits/5e-srd-api/compare/v3.20.0...v3.20.1) (2025-04-27)\n\n\n### Bug Fixes\n\n* :bug: Resolve full `Option` objects in `subrace.language_options` resolver ([#735](https://github.com/5e-bits/5e-srd-api/issues/735)) ([ef37127](https://github.com/5e-bits/5e-srd-api/commit/ef37127a68c0303c51d62d21835baa595b742435))\n\n# [3.20.0](https://github.com/5e-bits/5e-srd-api/compare/v3.19.0...v3.20.0) (2025-04-25)\n\n\n### Bug Fixes\n\n* **release:** Give workflow write permissions ([6c50c47](https://github.com/5e-bits/5e-srd-api/commit/6c50c47f0a599967c8e0ac6cea271452cbf696f9))\n* **release:** Set token on checkout ([158dc35](https://github.com/5e-bits/5e-srd-api/commit/158dc35bde5efb687e7c937c038e7ba54e9bc352))\n* **release:** Use PAT instead of normal GITHUB_TOKEN ([d518a8d](https://github.com/5e-bits/5e-srd-api/commit/d518a8d8eb1cca5ea399fb3d3ce97c7bd0fba618))\n\n\n### Features\n\n* **semver:** Updates changelog and npm version on release ([#733](https://github.com/5e-bits/5e-srd-api/issues/733)) ([40f60f3](https://github.com/5e-bits/5e-srd-api/commit/40f60f39ea1ed10d1b2b27f7e53c3d0fe6a7a9be))\n\n# Changelog\n\n## 2020-01-12\n\n- Make GET queries case insensitive for `name` where supported.\n- Fix Home link to work when you're on the Docs page\n\n## 2020-01-11\n\n- 100% Test coverage between unit and integration tests\n- Overloaded routes will be removed and moved onto routes that make sense.\n- General cleanup of the code base and breakup to make testing easier.\n\n## 2020-01-09\n\n- Add in Docker Compose\n\n## 2020-01-08\n\n- Add Prettier for auto formatting\n- Add in 404 support to stop timeouts\n- Add Heroku badge\n- Add in Jest testing framework\n- Add Bugsnag for error reporting\n\n## 2020-01-06\n\n- Update current docs to match new database changes\n\n## 2020-01-05\n\n- Fix race and subrace routes (#33)\n\n## 2020-01-04\n\n- Converted number indices to string indices based off of name\n- Added a Contribute link for the API\n- Move changes to Changelog\n- Fixed the navbar for the Docs to use the same partial\n\n## 2020-01-02\n\n- All of the database changes made in the last few months have finally landed\n- Replaced Slack chat with <a href=\"https://discord.gg/TQuYTv7\">Discord</a>\n- Added some HTTPS support\n\n## 2018-01-07\n\n- The Database and API are now OPEN SOURCE! Find it on my <a href=\"http://github.com/bagelbits\">github</a>\n- Updated changes from DevinOchman's <a href=\"https://github.com/adrpadua/5e-database/pull/3\">pull request</a>: New Races, Subraces, Traits\n\n## 2017-04-17\n\n- Created Slack chat group, which you can access <a href=\"http://dnd-5e-api-slack.herokuapp.com/\">here</a>. A place to ask questions and make suggestions etc.\n- Updated \"url\" members of every object to have 'www.' to avoid CORS errors\n"
  },
  {
    "path": "Dockerfile",
    "content": "# ---- Builder Stage ----\nFROM node:22-alpine AS builder\n\nWORKDIR /app\n\nCOPY package.json ./\n# Copy the package-lock.json that was freshly generated on your host\nCOPY package-lock.json ./\n\n# Clean existing node_modules just in case of Docker layer caching weirdness.\n# Then run `npm ci` which is generally recommended for CI/Docker if you have a package-lock.json.\nRUN rm -rf node_modules\nRUN npm ci\n\n# Copy the rest of the application source code\n# .dockerignore will handle exclusions like node_modules, dist, etc.\nCOPY . .\n\n# Build the application\n# This uses tsconfig.json to output to ./dist\nRUN npm run build\n\n# ---- Final Stage ----\nFROM node:22-alpine\n\nWORKDIR /app\n\n# Copy package.json and lock file (good practice)\nCOPY package.json ./\nCOPY package-lock.json* ./\n\n# Copy node_modules from builder stage - this includes all dependencies with scripts run\nCOPY --from=builder /app/node_modules ./node_modules/\n\n# Set environment to production AFTER dependencies are in place\nENV NODE_ENV=production\n\n# Copy built application from builder stage\nCOPY --from=builder /app/dist ./dist/\n\n# Copy entire source tree (needed for tests and potentially other runtime file access)\nCOPY --from=builder /app/src ./src/\n\n# Copy config files needed for tests/runtime\nCOPY --from=builder /app/vitest.config*.ts ./\nCOPY --from=builder /app/tsconfig.json ./\n\n# # Add non-root user for security\n# RUN addgroup -S appgroup && adduser -S appuser -G appgroup\n\n# # Change ownership of app directory to the non-root user AFTER user creation\n# RUN chown -R appuser:appgroup /app\n\n# USER appuser\n\n# Expose port (replace 3000 if different)\nEXPOSE 3000\n\n# Start the main process.\nCMD [\"node\", \"--experimental-specifier-resolution=node\", \"dist/src/start.js\"]\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) [2018-2020] [Adrian Padua, Christopher Ward]\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# 5e-srd-api\n\n[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/5e-bits/5e-srd-api/ci.yml?style=flat&logo=github&logoColor=white)](https://github.com/5e-bits/5e-srd-api/actions/workflows/ci.yml)\n[![Discord](https://img.shields.io/discord/656547667601653787?style=flat&logo=discord&logoColor=white)](https://discord.gg/TQuYTv7)\n![Uptime](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2F5e-bits%2Fdnd-uptime%2Fmain%2Fapi%2Fwebsite%2Fresponse-time.json)\n![Uptime](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2F5e-bits%2Fdnd-uptime%2Fmain%2Fapi%2Fwebsite%2Fuptime.json)\n\nREST API to access [D&amp;D 5th Edition SRD API](https://www.dnd5eapi.co/)\n\nTalk to us [on Discord!](https://discord.gg/TQuYTv7)\n\n## How to Run\n\nMake sure you have the latest version of the database:\n\n```shell\ndocker compose pull\n```\n\nThen run it with docker-compose:\n\n```shell\ndocker compose up --build\n```\n\n### M1/M2/M3 Macs\n\nThe command above pulls the latest image of the database from ghcr.io, which only targets the amd64 platform. If you are running on a different platform (like a Mac with Apple Silicon), you will need to build the image yourself. See the [5e-database](https://github.com/5e-bits/5e-database#how-to-run) repo for additional details.\n\n```shell\ncd ../\ngit clone https://github.com/5e-bits/5e-database.git\n```\n\nThen back over here in the 5e-srd-api repo, in the file `docker-compose.yml`, you can replace the line `image: bagelbits/5e-database` with `build: ../5e-database` (or whatever you named the custom db image).\n\n## Making API Requests\n\nMake API requests by using the root address:\n`http://localhost:3000/api/2014`\n\nYou should get a response with the available endpoints for the root:\n\n```json\n{\n  \"ability-scores\": \"/api/2014/ability-scores\",\n  \"classes\": \"/api/2014/classes\",\n  \"conditions\": \"/api/2014/conditions\",\n  \"damage-types\": \"/api/2014/damage-types\",\n  \"equipment-categories\": \"/api/2014/equipment-categories\",\n  \"equipment\": \"/api/2014/equipment\",\n  \"features\": \"/api/2014/features\",\n  \"languages\": \"/api/2014/languages\",\n  \"magic-schools\": \"/api/2014/magic-schools\",\n  \"monsters\": \"/api/2014/monsters\",\n  \"proficiencies\": \"/api/2014/proficiencies\",\n  \"races\": \"/api/2014/races\",\n  \"skills\": \"/api/2014/skills\",\n  \"spells\": \"/api/2014/spells\",\n  \"subclasses\": \"/api/2014/subclasses\",\n  \"subraces\": \"/api/2014/subraces\",\n  \"traits\": \"/api/2014/traits\",\n  \"weapon-properties\": \"/api/2014/weapon-properties\"\n}\n```\n\n### Versioning\n\nThe API is versioned by release years of the SRD. Currently only `/api/2014` is available. The next version will be `/api/2024`.\n\n## Working with a local image of 5e Database\n\nIf you are working on a feature which requires changes to both this repo, _and_ the 5e-database repo, it is useful to know how to connect the former to the latter for testing purposes. A simple process for doing so is as follows:\n\n1. In the file `docker-compose.yml`, you can replace the line `image: bagelbits/5e-database` with `build: [relativePathToDatabaseRepo]`. Make sure not to commit this change, as it is intended for local testing only.\n\n2. Run your branch of 5e-srd-api using the method outlined in the above section of this readme file. So long as there are no transient errors, the API should build successfully, and your changes to both repos should be noticeable.\n\n## Working with image resources from s3\n\nThe API uses s3 to store image files for monsters. The image files live in a bucket called `dnd-5e-api-images` under the `/monsters` folder.\n\nTo test locally, you can [use `localstack` to mock s3](https://docs.localstack.cloud/user-guide/aws/s3/). To do so, you will first need to install `localstack`,\n`awscli`, and `awslocal`. You can then run the following commands to configure and start the localstack container:\n\n```shell\nexport AWS_CONFIG_ENV=localstack_dev\nlocalstack start\nawslocal s3api create-bucket --bucket dnd-5e-api-images\nawslocal s3 cp aboleth.png s3://dnd-5e-api-images/monsters/\nnpm run dev\n```\n\nRequest the image by navigating to an image URL in a browser, or via HTTP request:\n\n```shell\ncurl http://localhost:3000/api/2014/monsters/aboleth.png --output downloaded-aboleth.png\n```\n\nWhen interacting with the image you should see logs in the terminal where you started localstack. You can also use [localstack's webui](https://app.localstack.cloud/dashboard) to view the bucket and\ncontents.\n\n## Data Issues\n\nIf you see anything wrong with the data itself, please open an issue or PR over [here.](https://github.com/5e-bits/5e-database/)\n\n## Running Tests\n\n### Unit Tests\n\nYou can run unit tests locally by using the command: `npm run test:unit`\n\n### Integration Tests\n\nIntegration tests need to be ran in the API docker container for them to function properly.\nIn order to run integration tests locally you can use the command: `npm run test:integration:local`\n\n## Documentation\n\nPublic facing API documentation lives [here.](https://www.dnd5eapi.co/docs)\n\nThe [docs repository](https://github.com/5e-bits/docs) contains the source for the public facing API documentation. It uses\n[Docusaurus](https://docusaurus.io/) to generate the site from a bundled OpenAPI spec.\n\nMore details on working with the OpenAPI spec can be found in the [`src/swagger`](src/swagger/) directory's [README](src/swagger/README.md). The most up-to-date bundled OpenAPI specs themselves are included in [the latest release](https://github.com/5e-bits/5e-srd-api/releases/latest) in both [JSON](https://github.com/5e-bits/5e-srd-api/releases/latest/download/openapi.json) and [YAML](https://github.com/5e-bits/5e-srd-api/releases/latest/download/openapi.yml) formats, which can be used to generate your own documentation, clients, etc.\n\nA [Postman collection](https://github.com/5e-bits/5e-srd-api/releases/latest/download/collection.postman.json) can also be found in the latest release. This can be imported into [the Postman HTTP client](https://www.postman.com/) to execute test requests against production & local deployments of the API.\n\n## Contributing\n\n- Fork this repository\n- Create a new branch for your work\n- Push up any changes to your branch, and open a pull request. Don't feel it needs to be perfect — incomplete work is totally fine. We'd love to help get it ready for merging.\n\n## Code of Conduct\n\nThe Code of Conduct for this repo can be found [here.](https://github.com/5e-bits/5e-srd-api/wiki#code-of-conduct)\n\n## Contributors\n\n<a href=\"https://github.com/5e-bits/5e-srd-api/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=5e-bits/5e-srd-api\" />\n</a>\n"
  },
  {
    "path": "app.json",
    "content": "{\n  \"name\": \"5e-srd-api\",\n  \"scripts\": {},\n  \"env\": {\n    \"AWS_ACCESS_KEY_ID\": {\n      \"required\": true\n    },\n    \"AWS_SECRET_ACCESS_KEY\": {\n      \"required\": true\n    },\n    \"BUGSNAG_API_KEY\": {\n      \"required\": true\n    },\n    \"GRAPHQL_API_KEY\": {\n      \"required\": true\n    },\n    \"MONGODB_URI\": {\n      \"required\": true\n    },\n    \"NODE_ENV\": {\n      \"required\": true\n    },\n    \"REALM_API_KEY\": {\n      \"required\": true\n    },\n    \"REALM_APP_ID\": {\n      \"required\": true\n    }\n  },\n  \"formation\": {\n    \"web\": {\n      \"quantity\": 1\n    }\n  },\n  \"addons\": [\"bugsnag:tauron\", \"heroku-redis:mini\"],\n  \"buildpacks\": [\n    {\n      \"url\": \"heroku/nodejs\"\n    }\n  ],\n  \"stack\": \"container\"\n}\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "services:\n  db:\n    image: ghcr.io/5e-bits/5e-database:latest\n    # build: ../5e-database\n    ports:\n      - '27017:27017'\n\n  cache:\n    image: redis:6.2.5\n    ports:\n      - '6379:6379'\n\n  api:\n    environment:\n      MONGODB_URI: mongodb://db/5e-database\n      REDIS_URL: redis://cache:6379\n    build: .\n    ports:\n      - '3000:3000'\n    depends_on:\n      - db\n      - cache\n"
  },
  {
    "path": "eslint.config.js",
    "content": "import eslint from '@eslint/js'\nimport stylistic from '@stylistic/eslint-plugin'\nimport eslintConfigPrettier from 'eslint-config-prettier'\nimport { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript'\nimport { importX } from 'eslint-plugin-import-x'\nimport globals from 'globals'\nimport tseslint from 'typescript-eslint'\n\nexport default tseslint.config(\n  {\n    name: 'base-ignore',\n    ignores: ['**/coverage/**', '**/dist/**', '**/node_modules/**']\n  },\n  eslint.configs.recommended,\n  // Main TypeScript and JS linting configuration\n  {\n    name: 'typescript-and-imports-x',\n    files: ['**/*.ts', '**/*.js'],\n    extends: [\n      ...tseslint.configs.recommended,\n      importX.flatConfigs.recommended,\n      importX.flatConfigs.typescript\n    ],\n    plugins: {\n      '@stylistic': stylistic,\n      'import-x': importX\n    },\n    languageOptions: {\n      parserOptions: {\n        project: true,\n        tsconfigRootDir: import.meta.dirname\n      },\n      globals: {\n        ...globals.node,\n        ...globals.browser,\n        ...globals.jquery\n      }\n    },\n    rules: {\n      '@typescript-eslint/no-explicit-any': 'off',\n      '@typescript-eslint/strict-boolean-expressions': 'error',\n      'import-x/order': [\n        'error',\n        {\n          groups: [\n            'builtin',\n            'external',\n            'internal',\n            ['parent', 'sibling', 'index'],\n            'object',\n            'type'\n          ],\n          'newlines-between': 'always',\n          alphabetize: { order: 'asc', caseInsensitive: true },\n          pathGroups: [{ pattern: '@/**', group: 'internal', position: 'after' }],\n          pathGroupsExcludedImportTypes: ['builtin', 'external', 'object', 'type']\n        }\n      ],\n      'import-x/no-duplicates': 'error',\n      'import-x/no-named-as-default': 'off',\n      'import-x/no-named-as-default-member': 'off'\n    },\n    settings: {\n      'import-x/resolver-next': [\n        createTypeScriptImportResolver({\n          alwaysTryTypes: true,\n          project: './tsconfig.json'\n        })\n      ]\n    }\n  },\n  eslintConfigPrettier // MUST BE LAST\n)\n"
  },
  {
    "path": "heroku.yml",
    "content": "build:\n  docker:\n    web: Dockerfile\nrun:\n  web: node dist/src/start.js\n"
  },
  {
    "path": "nodemon.json",
    "content": "{\n    \"verbose\": false,\n    \"ignore\": [\n        \"*.test.*\",\n        \"dist/*\",\n        \"node_modules/*\",\n        \"collection.postman.json\",\n        \"tmp/*\"\n    ],\n    \"ext\": \"js,ts,ejs,json,yml\"\n}\n"
  },
  {
    "path": "openapi-to-postman.json",
    "content": "{\n  \"folderStrategy\": \"Tags\",\n  \"includeAuthInfoInExample\": true,\n  \"optimizeConversion\": true,\n  \"stackLimit\": 50,\n  \"requestParametersResolution\": \"Example\",\n  \"exampleParametersResolution\": \"Example\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"dnd-5e-srd-api\",\n  \"version\": \"5.4.0\",\n  \"engines\": {\n    \"node\": \"22.x\",\n    \"npm\": \">=10.8.0\"\n  },\n  \"type\": \"module\",\n  \"private\": true,\n  \"scripts\": {\n    \"build:ts\": \"tsc && tsc-alias\",\n    \"build\": \"npm-run-all --sequential clean bundle-swagger build:ts copy-assets\",\n    \"clean\": \"rimraf dist/*\",\n    \"copy-assets\": \"mkdir -p dist && cp -R src/css dist/src && cp -R src/public dist/src\",\n    \"start\": \"npm run build && node dist/src/start.js\",\n    \"dev\": \"nodemon npm run start\",\n    \"lint\": \"eslint . --config eslint.config.js\",\n    \"lint:fix\": \"eslint . --config eslint.config.js --fix\",\n    \"validate-swagger\": \"redocly lint src/swagger/swagger.yml\",\n    \"bundle-swagger\": \"npm run validate-swagger && redocly bundle --output dist/openapi.yml src/swagger/swagger.yml && redocly bundle --output dist/openapi.json src/swagger/swagger.yml\",\n    \"gen-postman\": \"npm run bundle-swagger && openapi2postmanv2 -s dist/openapi.yml -o dist/collection.postman.json -c openapi-to-postman.json\",\n    \"test\": \"npm run test:unit && npm run test:integration:local\",\n    \"test:unit\": \"vitest run\",\n    \"test:integration\": \"vitest run --config ./vitest.config.integration.ts\",\n    \"test:integration:local\": \"docker compose pull && docker compose build && docker compose run --use-aliases api npm run test:integration\"\n  },\n  \"dependencies\": {\n    \"@apollo/server\": \"^5.5.0\",\n    \"@as-integrations/express5\": \"^1.1.2\",\n    \"@aws-sdk/client-s3\": \"^3.994.0\",\n    \"@bugsnag/js\": \"^8.8.1\",\n    \"@bugsnag/plugin-express\": \"^8.8.0\",\n    \"@graphql-tools/schema\": \"^10.0.31\",\n    \"@redocly/cli\": \"^2.25.4\",\n    \"@typegoose/typegoose\": \"^13.2.1\",\n    \"body-parser\": \"^2.2.2\",\n    \"class-validator\": \"^0.15.1\",\n    \"cookie-parser\": \"^1.4.7\",\n    \"cors\": \"^2.8.6\",\n    \"debug\": \"^4.4.3\",\n    \"ejs\": \"^5.0.2\",\n    \"escape-string-regexp\": \"^5.0.0\",\n    \"express\": \"^5.1.0\",\n    \"express-rate-limit\": \"^8.2.2\",\n    \"graphql\": \"^16.11.0\",\n    \"graphql-depth-limit\": \"^1.1.0\",\n    \"mongoose\": \"~9.2.3\",\n    \"morgan\": \"^1.10.1\",\n    \"node-fetch\": \"^3.1.1\",\n    \"redis\": \"^5.10.0\",\n    \"reflect-metadata\": \"^0.2.2\",\n    \"serve-favicon\": \"^2.5.1\",\n    \"type-graphql\": \"^2.0.0-rc.2\",\n    \"zod\": \"^4.3.6\"\n  },\n  \"devDependencies\": {\n    \"@eslint/js\": \"^10.0.1\",\n    \"@faker-js/faker\": \"^10.3.0\",\n    \"@stylistic/eslint-plugin\": \"^5.7.1\",\n    \"@types/cors\": \"^2.8.19\",\n    \"@types/express\": \"^5.0.5\",\n    \"@types/fs-extra\": \"^11.0.4\",\n    \"@types/graphql-depth-limit\": \"^1.1.3\",\n    \"@types/morgan\": \"^1.9.10\",\n    \"@types/node\": \"^25.5.2\",\n    \"@types/shelljs\": \"^0.10.0\",\n    \"@types/supertest\": \"^7.2.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.57.2\",\n    \"@typescript-eslint/parser\": \"^8.58.2\",\n    \"eslint\": \"^10.2.0\",\n    \"eslint-config-prettier\": \"^10.1.8\",\n    \"eslint-import-resolver-typescript\": \"^4.4.4\",\n    \"eslint-plugin-ejs\": \"^0.0.2\",\n    \"eslint-plugin-import-x\": \"^4.16.1\",\n    \"eslint-plugin-prettier\": \"^5.5.5\",\n    \"fishery\": \"^2.3.1\",\n    \"fs-extra\": \"^11.3.1\",\n    \"globals\": \"^17.0.0\",\n    \"mongodb-memory-server\": \"^11.0.1\",\n    \"node-mocks-http\": \"^1.17.2\",\n    \"nodemon\": \"^3.1.10\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"openapi-to-postmanv2\": \"^5.3.5\",\n    \"prettier\": \"^3.8.2\",\n    \"rimraf\": \"^6.1.3\",\n    \"shelljs\": \"^0.10.0\",\n    \"supertest\": \"^7.2.2\",\n    \"ts-node\": \"^10.9.2\",\n    \"tsc-alias\": \"^1.8.16\",\n    \"typedoc\": \"^0.28.16\",\n    \"typescript\": \"^5.9.3\",\n    \"typescript-eslint\": \"^8.58.0\",\n    \"vitest\": \"^4.1.2\",\n    \"yaml\": \"^2.8.2\"\n  }\n}\n"
  },
  {
    "path": "release-please-config.json",
    "content": "{\n  \"release-type\": \"node\",\n  \"packages\": {\n    \".\": {\n      \"release-type\": \"node\",\n      \"package-name\": \"5e-srd-api\",\n      \"changelog-sections\": [\n        {\n          \"type\": \"feat\",\n          \"section\": \"Features\"\n        },\n        {\n          \"type\": \"fix\",\n          \"section\": \"Bug Fixes\"\n        },\n        {\n          \"type\": \"deps\",\n          \"section\": \"Dependencies\",\n          \"hidden\": false\n        }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "src/controllers/api/2014/abilityScoreController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport AbilityScoreModel from '@/models/2014/abilityScore'\n\nexport default new SimpleController(AbilityScoreModel)\n"
  },
  {
    "path": "src/controllers/api/2014/alignmentController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport AlignmentModel from '@/models/2014/alignment'\n\nexport default new SimpleController(AlignmentModel)\n"
  },
  {
    "path": "src/controllers/api/2014/backgroundController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport Background from '@/models/2014/background'\n\nexport default new SimpleController(Background)\n"
  },
  {
    "path": "src/controllers/api/2014/classController.ts",
    "content": "import { NextFunction, Request, Response } from 'express'\n\nimport SimpleController from '@/controllers/simpleController'\nimport Class from '@/models/2014/class'\nimport Feature from '@/models/2014/feature'\nimport Level from '@/models/2014/level'\nimport Proficiency from '@/models/2014/proficiency'\nimport Spell from '@/models/2014/spell'\nimport Subclass from '@/models/2014/subclass'\nimport {\n  ClassLevelsQuerySchema,\n  LevelParamsSchema,\n  ShowParamsSchema,\n  SpellIndexQuerySchema\n} from '@/schemas/schemas'\nimport { escapeRegExp, ResourceList } from '@/util'\n\nconst simpleController = new SimpleController(Class)\ninterface ShowLevelsForClassQuery {\n  'class.url': string\n  $or: Record<string, null | Record<string, RegExp>>[]\n}\n\nexport const index = async (req: Request, res: Response, next: NextFunction) =>\n  simpleController.index(req, res, next)\nexport const show = async (req: Request, res: Response, next: NextFunction) =>\n  simpleController.show(req, res, next)\n\nexport const showLevelsForClass = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    // Validate path and query parameters\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    const validatedQuery = ClassLevelsQuerySchema.safeParse(req.query)\n\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    if (!validatedQuery.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid query parameters', details: validatedQuery.error.issues })\n    }\n\n    const { index } = validatedParams.data\n    const { subclass } = validatedQuery.data\n\n    const searchQueries: ShowLevelsForClassQuery = {\n      'class.url': '/api/2014/classes/' + index,\n      $or: [{ subclass: null }]\n    }\n\n    if (subclass !== undefined) {\n      searchQueries.$or.push({\n        'subclass.url': { $regex: new RegExp(escapeRegExp(subclass), 'i') }\n      })\n    }\n\n    const data = await Level.find(searchQueries).sort({ level: 'asc' })\n    if (data !== null && data !== undefined && data.length > 0) {\n      return res.status(200).json(data)\n    } else {\n      return res.status(404).json({ error: 'Not found' })\n    }\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const showLevelForClass = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    // Validate path parameters\n    const validatedParams = LevelParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index, level } = validatedParams.data\n\n    const urlString = '/api/2014/classes/' + index + '/levels/' + level\n\n    const data = await Level.findOne({ url: urlString })\n    if (!data) return next()\n    return res.status(200).json(data)\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const showMulticlassingForClass = async (\n  req: Request,\n  res: Response,\n  next: NextFunction\n) => {\n  try {\n    const urlString = '/api/2014/classes/' + req.params.index\n\n    const data = await Class.findOne({ url: urlString })\n    return res.status(200).json(data?.multi_classing)\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const showSubclassesForClass = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    // Validate path parameters\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    const urlString = '/api/2014/classes/' + index\n\n    const data = await Subclass.find({ 'class.url': urlString })\n      .select({ index: 1, name: 1, url: 1, _id: 0 })\n      .sort({ url: 'asc', level: 'asc' })\n    if (data !== null && data !== undefined && data.length > 0) {\n      return res.status(200).json(ResourceList(data))\n    } else {\n      return res.status(404).json({ error: 'Not found' })\n    }\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const showStartingEquipmentForClass = async (\n  req: Request,\n  res: Response,\n  next: NextFunction\n) => {\n  try {\n    const data = await Class.findOne({ index: req.params.index })\n    return res.status(200).json({\n      starting_equipment: data?.starting_equipment,\n      starting_equipment_options: data?.starting_equipment_options\n    })\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const showSpellcastingForClass = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    // Validate path parameters\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    const data = await Class.findOne({ index: index })\n    if (\n      data !== null &&\n      data !== undefined &&\n      data.spellcasting !== null &&\n      data.spellcasting !== undefined\n    ) {\n      return res.status(200).json(data.spellcasting)\n    } else {\n      return res.status(404).json({ error: 'Not found' })\n    }\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const showSpellsForClass = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    const validatedQuery = SpellIndexQuerySchema.safeParse(req.query)\n\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n\n    if (!validatedQuery.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid query parameters', details: validatedQuery.error.issues })\n    }\n\n    const { index } = validatedParams.data\n    const { level } = validatedQuery.data\n\n    // Check if class exists first\n    const classExists = await Class.findOne({ index }).lean()\n    if (!classExists) {\n      return res.status(404).json({ error: 'Not found' })\n    }\n\n    const urlString = '/api/2014/classes/' + index\n    const findQuery: { 'classes.url': string; level?: { $in: number[] } } = {\n      'classes.url': urlString\n    }\n\n    if (level !== undefined) {\n      findQuery.level = { $in: level.map(Number) }\n    }\n\n    const data = await Spell.find(findQuery)\n      .select({ index: 1, level: 1, name: 1, url: 1, _id: 0 })\n      .sort({ level: 'asc', url: 'asc' })\n    return res.status(200).json(ResourceList(data))\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const showSpellsForClassAndLevel = async (\n  req: Request,\n  res: Response,\n  next: NextFunction\n) => {\n  try {\n    // Validate path parameters\n    const validatedParams = LevelParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index, level: classLevel } = validatedParams.data\n\n    // Find the level data for the class\n    const levelData = await Level.findOne({ 'class.index': index, level: classLevel }).lean()\n\n    let maxSpellLevel = -1 // Default to -1 indicating no spellcasting ability found\n\n    if (levelData?.spellcasting) {\n      maxSpellLevel = 0 // Has spellcasting, so at least cantrips (level 0) might be available\n      const spellcasting = levelData.spellcasting\n      for (let i = 9; i >= 1; i--) {\n        // Check if the spell slot exists and is greater than 0\n        const spellSlotKey = `spell_slots_level_${i}` as keyof typeof spellcasting\n        if (spellcasting[spellSlotKey] != null && spellcasting[spellSlotKey]! > 0) {\n          maxSpellLevel = i\n          break // Found the highest level\n        }\n      }\n    }\n\n    if (maxSpellLevel < 0) {\n      return res.status(200).json(ResourceList([]))\n    }\n\n    const urlString = '/api/2014/classes/' + index\n\n    // Find spells for the class with level <= maxSpellLevel\n    const spellData = await Spell.find({\n      'classes.url': urlString,\n      level: { $lte: maxSpellLevel, $gte: 0 } // Query uses maxSpellLevel\n    })\n      .select({ index: 1, name: 1, url: 1, _id: 0 })\n      .sort({ index: 'asc' })\n      .lean() // Use lean for performance as we only read data\n\n    return res.status(200).json(ResourceList(spellData))\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const showFeaturesForClass = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    // Validate path parameters\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    // Check if class exists first\n    const classExists = await Class.findOne({ index }).lean()\n    if (!classExists) {\n      return res.status(404).json({ error: 'Not found' })\n    }\n\n    const urlString = '/api/2014/classes/' + index\n\n    const data = await Feature.find({\n      'class.url': urlString\n    })\n      .select({ index: 1, name: 1, url: 1, _id: 0 })\n      .sort({ level: 'asc', url: 'asc' })\n    return res.status(200).json(ResourceList(data))\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const showFeaturesForClassAndLevel = async (\n  req: Request,\n  res: Response,\n  next: NextFunction\n) => {\n  try {\n    // Validate path parameters\n    const validatedParams = LevelParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index, level } = validatedParams.data\n\n    const urlString = '/api/2014/classes/' + index\n\n    const data = await Feature.find({\n      'class.url': urlString,\n      level\n    })\n      .select({ index: 1, name: 1, url: 1, _id: 0 })\n      .sort({ level: 'asc', url: 'asc' })\n    return res.status(200).json(ResourceList(data))\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const showProficienciesForClass = async (\n  req: Request,\n  res: Response,\n  next: NextFunction\n) => {\n  try {\n    // Validate path parameters\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    // Check if class exists first\n    const classExists = await Class.findOne({ index }).lean()\n    if (!classExists) {\n      return res.status(404).json({ error: 'Not found' })\n    }\n\n    const urlString = '/api/2014/classes/' + index\n\n    const data = await Proficiency.find({ 'classes.url': urlString })\n      .select({ index: 1, name: 1, url: 1, _id: 0 })\n      .sort({ index: 'asc' })\n    return res.status(200).json(ResourceList(data))\n  } catch (err) {\n    next(err)\n  }\n}\n"
  },
  {
    "path": "src/controllers/api/2014/conditionController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport ConditionModel from '@/models/2014/condition'\n\nexport default new SimpleController(ConditionModel)\n"
  },
  {
    "path": "src/controllers/api/2014/damageTypeController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport DamageType from '@/models/2014/damageType'\n\nexport default new SimpleController(DamageType)\n"
  },
  {
    "path": "src/controllers/api/2014/equipmentCategoryController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport EquipmentCategory from '@/models/2014/equipmentCategory'\n\nexport default new SimpleController(EquipmentCategory)\n"
  },
  {
    "path": "src/controllers/api/2014/equipmentController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport Equipment from '@/models/2014/equipment'\n\nexport default new SimpleController(Equipment)\n"
  },
  {
    "path": "src/controllers/api/2014/featController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport Feat from '@/models/2014/feat'\n\nexport default new SimpleController(Feat)\n"
  },
  {
    "path": "src/controllers/api/2014/featureController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport Feature from '@/models/2014/feature'\n\nexport default new SimpleController(Feature)\n"
  },
  {
    "path": "src/controllers/api/2014/languageController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport Language from '@/models/2014/language'\n\nexport default new SimpleController(Language)\n"
  },
  {
    "path": "src/controllers/api/2014/magicItemController.ts",
    "content": "import { NextFunction, Request, Response } from 'express'\n\nimport MagicItem from '@/models/2014/magicItem'\nimport { NameQuerySchema, ShowParamsSchema } from '@/schemas/schemas'\nimport { escapeRegExp, redisClient, ResourceList } from '@/util'\n\ninterface IndexQuery {\n  name?: { $regex: RegExp }\n}\n\nexport const index = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    // Validate query parameters\n    const validatedQuery = NameQuerySchema.safeParse(req.query)\n    if (!validatedQuery.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid query parameters', details: validatedQuery.error.issues })\n    }\n    const { name } = validatedQuery.data\n\n    const searchQueries: IndexQuery = {}\n    if (name !== undefined) {\n      searchQueries.name = { $regex: new RegExp(escapeRegExp(name), 'i') }\n    }\n\n    const redisKey = req.originalUrl\n    const data = await redisClient.get(redisKey)\n\n    if (data !== null && data !== undefined && data !== '') {\n      res.status(200).json(JSON.parse(data))\n    } else {\n      const data = await MagicItem.find(searchQueries)\n        .select({ index: 1, name: 1, url: 1, _id: 0 })\n        .sort({ index: 'asc' })\n      const jsonData = ResourceList(data)\n      redisClient.set(redisKey, JSON.stringify(jsonData))\n      return res.status(200).json(jsonData)\n    }\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const show = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    // Validate path parameters\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    const data = await MagicItem.findOne({ index: index })\n    if (data === null || data === undefined) return next()\n    return res.status(200).json(data)\n  } catch (err) {\n    next(err)\n  }\n}\n"
  },
  {
    "path": "src/controllers/api/2014/magicSchoolController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport MagicSchool from '@/models/2014/magicSchool'\n\nexport default new SimpleController(MagicSchool)\n"
  },
  {
    "path": "src/controllers/api/2014/monsterController.ts",
    "content": "import { NextFunction, Request, Response } from 'express'\n\nimport Monster from '@/models/2014/monster'\nimport { MonsterIndexQuerySchema, ShowParamsSchema } from '@/schemas/schemas'\nimport { escapeRegExp, redisClient, ResourceList } from '@/util'\n\ninterface IndexQuery {\n  name?: { $regex: RegExp }\n  challenge_rating?: { $in: number[] }\n}\n\nexport const index = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    // Validate query parameters\n    const validatedQuery = MonsterIndexQuerySchema.safeParse(req.query)\n    if (!validatedQuery.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid query parameters', details: validatedQuery.error.issues })\n    }\n    const { name, challenge_rating } = validatedQuery.data\n\n    const searchQueries: IndexQuery = {}\n    if (name !== undefined) {\n      searchQueries.name = { $regex: new RegExp(escapeRegExp(name), 'i') }\n    }\n    if (challenge_rating !== undefined && challenge_rating.length > 0) {\n      searchQueries.challenge_rating = { $in: challenge_rating }\n    }\n\n    const redisKey = req.originalUrl\n    const data = await redisClient.get(redisKey)\n\n    if (data !== null) {\n      res.status(200).json(JSON.parse(data))\n    } else {\n      const data = await Monster.find(searchQueries)\n        .select({ index: 1, name: 1, url: 1, _id: 0 })\n        .sort({ index: 'asc' })\n      const jsonData = ResourceList(data)\n      redisClient.set(redisKey, JSON.stringify(jsonData))\n      return res.status(200).json(jsonData)\n    }\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const show = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    // Validate path parameters\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    const data = await Monster.findOne({ index: index })\n    if (!data) return next()\n    return res.status(200).json(data)\n  } catch (err) {\n    next(err)\n  }\n}\n"
  },
  {
    "path": "src/controllers/api/2014/proficiencyController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport Proficiency from '@/models/2014/proficiency'\n\nexport default new SimpleController(Proficiency)\n"
  },
  {
    "path": "src/controllers/api/2014/raceController.ts",
    "content": "import { NextFunction, Request, Response } from 'express'\n\nimport SimpleController from '@/controllers/simpleController'\nimport Proficiency from '@/models/2014/proficiency'\nimport Race from '@/models/2014/race'\nimport Subrace from '@/models/2014/subrace'\nimport Trait from '@/models/2014/trait'\nimport { ShowParamsSchema } from '@/schemas/schemas'\nimport { ResourceList } from '@/util/data'\n\nconst simpleController = new SimpleController(Race)\n\nexport const index = async (req: Request, res: Response, next: NextFunction) =>\n  simpleController.index(req, res, next)\nexport const show = async (req: Request, res: Response, next: NextFunction) =>\n  simpleController.show(req, res, next)\n\nexport const showSubracesForRace = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    // Validate path parameters\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    const urlString = '/api/2014/races/' + index\n\n    const data = await Subrace.find({ 'race.url': urlString }).select({\n      index: 1,\n      name: 1,\n      url: 1,\n      _id: 0\n    })\n    return res.status(200).json(ResourceList(data))\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const showTraitsForRace = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    // Validate path parameters\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    const urlString = '/api/2014/races/' + index\n\n    const data = await Trait.find({ 'races.url': urlString }).select({\n      index: 1,\n      name: 1,\n      url: 1,\n      _id: 0\n    })\n    return res.status(200).json(ResourceList(data))\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const showProficienciesForRace = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    // Validate path parameters\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    const urlString = '/api/2014/races/' + index\n\n    const data = await Proficiency.find({ 'races.url': urlString })\n      .select({ index: 1, name: 1, url: 1, _id: 0 })\n      .sort({ index: 'asc' })\n    return res.status(200).json(ResourceList(data))\n  } catch (err) {\n    next(err)\n  }\n}\n"
  },
  {
    "path": "src/controllers/api/2014/ruleController.ts",
    "content": "import { NextFunction, Request, Response } from 'express'\n\nimport Rule from '@/models/2014/rule'\nimport { NameDescQuerySchema, ShowParamsSchema } from '@/schemas/schemas'\nimport { escapeRegExp, redisClient, ResourceList } from '@/util'\n\ninterface IndexQuery {\n  name?: { $regex: RegExp }\n  desc?: { $regex: RegExp }\n}\n\nexport const index = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    // Validate query parameters\n    const validatedQuery = NameDescQuerySchema.safeParse(req.query)\n    if (!validatedQuery.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid query parameters', details: validatedQuery.error.issues })\n    }\n    const { name, desc } = validatedQuery.data\n\n    const searchQueries: IndexQuery = {}\n    if (name !== undefined) {\n      searchQueries.name = { $regex: new RegExp(escapeRegExp(name), 'i') }\n    }\n    if (desc !== undefined) {\n      searchQueries.desc = { $regex: new RegExp(escapeRegExp(desc), 'i') }\n    }\n\n    const redisKey = req.originalUrl\n    const data = await redisClient.get(redisKey)\n\n    if (data !== null) {\n      res.status(200).json(JSON.parse(data))\n    } else {\n      const data = await Rule.find(searchQueries)\n        .select({ index: 1, name: 1, url: 1, _id: 0 })\n        .sort({ index: 'asc' })\n      const jsonData = ResourceList(data)\n      redisClient.set(redisKey, JSON.stringify(jsonData))\n      return res.status(200).json(jsonData)\n    }\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const show = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    // Validate path parameters\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    const data = await Rule.findOne({ index: index })\n    if (!data) return next()\n    return res.status(200).json(data)\n  } catch (err) {\n    next(err)\n  }\n}\n"
  },
  {
    "path": "src/controllers/api/2014/ruleSectionController.ts",
    "content": "import { NextFunction, Request, Response } from 'express'\n\nimport RuleSection from '@/models/2014/ruleSection'\nimport { NameDescQuerySchema, ShowParamsSchema } from '@/schemas/schemas'\nimport { escapeRegExp, redisClient, ResourceList } from '@/util'\n\ninterface IndexQuery {\n  name?: { $regex: RegExp }\n  desc?: { $regex: RegExp }\n}\n\nexport const index = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    // Validate query parameters\n    const validatedQuery = NameDescQuerySchema.safeParse(req.query)\n    if (!validatedQuery.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid query parameters', details: validatedQuery.error.issues })\n    }\n    const { name, desc } = validatedQuery.data\n\n    const searchQueries: IndexQuery = {}\n    if (name !== undefined) {\n      searchQueries.name = { $regex: new RegExp(escapeRegExp(name), 'i') }\n    }\n    if (desc !== undefined) {\n      searchQueries.desc = { $regex: new RegExp(escapeRegExp(desc), 'i') }\n    }\n\n    const redisKey = req.originalUrl\n    const data = await redisClient.get(redisKey)\n\n    if (data !== null) {\n      res.status(200).json(JSON.parse(data))\n    } else {\n      const data = await RuleSection.find(searchQueries)\n        .select({ index: 1, name: 1, url: 1, _id: 0 })\n        .sort({ index: 'asc' })\n      const jsonData = ResourceList(data)\n      redisClient.set(redisKey, JSON.stringify(jsonData))\n      return res.status(200).json(jsonData)\n    }\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const show = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    // Validate path parameters\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    const data = await RuleSection.findOne({ index: index })\n    if (!data) return next()\n    return res.status(200).json(data)\n  } catch (err) {\n    next(err)\n  }\n}\n"
  },
  {
    "path": "src/controllers/api/2014/skillController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport Skill from '@/models/2014/skill'\n\nexport default new SimpleController(Skill)\n"
  },
  {
    "path": "src/controllers/api/2014/spellController.ts",
    "content": "import { NextFunction, Request, Response } from 'express'\n\nimport Spell from '@/models/2014/spell'\nimport { ShowParamsSchema, SpellIndexQuerySchema } from '@/schemas/schemas'\nimport { escapeRegExp, redisClient, ResourceList } from '@/util'\n\ninterface IndexQuery {\n  name?: { $regex: RegExp }\n  level?: { $in: number[] }\n  'school.name'?: { $in: RegExp[] }\n}\n\nexport const index = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    const validatedQuery = SpellIndexQuerySchema.safeParse(req.query)\n    if (!validatedQuery.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid query parameters', details: validatedQuery.error.issues })\n    }\n    const { name, level, school } = validatedQuery.data\n\n    const searchQueries: IndexQuery = {}\n    if (name !== undefined) {\n      searchQueries.name = { $regex: new RegExp(escapeRegExp(name), 'i') }\n    }\n\n    if (level !== undefined) {\n      searchQueries.level = { $in: level.map(Number) }\n    }\n\n    if (school !== undefined) {\n      const schoolRegex = school.map((s) => new RegExp(escapeRegExp(s), 'i'))\n      searchQueries['school.name'] = { $in: schoolRegex }\n    }\n\n    const redisKey = req.originalUrl\n    const data = await redisClient.get(redisKey)\n\n    if (data !== null) {\n      res.status(200).json(JSON.parse(data))\n    } else {\n      const data = await Spell.find(searchQueries)\n        .select({ index: 1, level: 1, name: 1, url: 1, _id: 0 })\n        .sort({ index: 'asc' })\n      const jsonData = ResourceList(data)\n      redisClient.set(redisKey, JSON.stringify(jsonData))\n      return res.status(200).json(jsonData)\n    }\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const show = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    const data = await Spell.findOne({ index: index })\n    if (!data) return next()\n    return res.status(200).json(data)\n  } catch (err) {\n    next(err)\n  }\n}\n"
  },
  {
    "path": "src/controllers/api/2014/subclassController.ts",
    "content": "import { NextFunction, Request, Response } from 'express'\n\nimport SimpleController from '@/controllers/simpleController'\nimport Feature from '@/models/2014/feature'\nimport Level from '@/models/2014/level'\nimport Subclass from '@/models/2014/subclass'\nimport { LevelParamsSchema, ShowParamsSchema } from '@/schemas/schemas'\nimport { ResourceList } from '@/util/data'\n\nconst simpleController = new SimpleController(Subclass)\n\nexport const index = async (req: Request, res: Response, next: NextFunction) =>\n  simpleController.index(req, res, next)\nexport const show = async (req: Request, res: Response, next: NextFunction) =>\n  simpleController.show(req, res, next)\n\nexport const showLevelsForSubclass = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    const urlString = '/api/2014/subclasses/' + index\n\n    const data = await Level.find({ 'subclass.url': urlString }).sort({ level: 'asc' })\n    return res.status(200).json(data)\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const showLevelForSubclass = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    const validatedParams = LevelParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index, level } = validatedParams.data\n\n    const urlString = '/api/2014/subclasses/' + index + '/levels/' + level\n\n    const data = await Level.findOne({ url: urlString })\n    if (!data) return next()\n    return res.status(200).json(data)\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const showFeaturesForSubclass = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    const urlString = '/api/2014/subclasses/' + index\n\n    const data = await Feature.find({\n      'subclass.url': urlString\n    })\n      .select({ index: 1, name: 1, url: 1, _id: 0 })\n      .sort({ level: 'asc', url: 'asc' })\n    return res.status(200).json(ResourceList(data))\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const showFeaturesForSubclassAndLevel = async (\n  req: Request,\n  res: Response,\n  next: NextFunction\n) => {\n  try {\n    const validatedParams = LevelParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index, level } = validatedParams.data\n\n    const urlString = '/api/2014/subclasses/' + index\n\n    const data = await Feature.find({\n      level: level,\n      'subclass.url': urlString\n    })\n      .select({ index: 1, name: 1, url: 1, _id: 0 })\n      .sort({ level: 'asc', url: 'asc' })\n    return res.status(200).json(ResourceList(data))\n  } catch (err) {\n    next(err)\n  }\n}\n"
  },
  {
    "path": "src/controllers/api/2014/subraceController.ts",
    "content": "import { NextFunction, Request, Response } from 'express'\n\nimport SimpleController from '@/controllers/simpleController'\nimport Proficiency from '@/models/2014/proficiency'\nimport Subrace from '@/models/2014/subrace'\nimport Trait from '@/models/2014/trait'\nimport { ShowParamsSchema } from '@/schemas/schemas'\nimport { ResourceList } from '@/util/data'\n\nconst simpleController = new SimpleController(Subrace)\n\nexport const index = async (req: Request, res: Response, next: NextFunction) =>\n  simpleController.index(req, res, next)\nexport const show = async (req: Request, res: Response, next: NextFunction) =>\n  simpleController.show(req, res, next)\n\nexport const showTraitsForSubrace = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    // Validate path parameters\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    const urlString = '/api/2014/subraces/' + index\n    const data = await Trait.find({ 'subraces.url': urlString }).select({\n      index: 1,\n      name: 1,\n      url: 1,\n      _id: 0\n    })\n\n    return res.status(200).json(ResourceList(data))\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const showProficienciesForSubrace = async (\n  req: Request,\n  res: Response,\n  next: NextFunction\n) => {\n  try {\n    // Validate path parameters\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    const urlString = '/api/2014/subraces/' + index\n\n    const data = await Proficiency.find({ 'races.url': urlString })\n      .select({ index: 1, name: 1, url: 1, _id: 0 })\n      .sort({ index: 'asc' })\n    return res.status(200).json(ResourceList(data))\n  } catch (err) {\n    next(err)\n  }\n}\n"
  },
  {
    "path": "src/controllers/api/2014/traitController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport Trait from '@/models/2014/trait'\n\nexport default new SimpleController(Trait)\n"
  },
  {
    "path": "src/controllers/api/2014/weaponPropertyController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport WeaponProperty from '@/models/2014/weaponProperty'\n\nexport default new SimpleController(WeaponProperty)\n"
  },
  {
    "path": "src/controllers/api/2024/abilityScoreController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport AbilityScoreModel from '@/models/2024/abilityScore'\n\nexport default new SimpleController(AbilityScoreModel)\n"
  },
  {
    "path": "src/controllers/api/2024/alignmentController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport AlignmentModel from '@/models/2024/alignment'\n\nexport default new SimpleController(AlignmentModel)\n"
  },
  {
    "path": "src/controllers/api/2024/backgroundController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport BackgroundModel from '@/models/2024/background'\n\nexport default new SimpleController(BackgroundModel)\n"
  },
  {
    "path": "src/controllers/api/2024/conditionController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport ConditionModel from '@/models/2024/condition'\n\nexport default new SimpleController(ConditionModel)\n"
  },
  {
    "path": "src/controllers/api/2024/damageTypeController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport DamageTypeModel from '@/models/2024/damageType'\n\nexport default new SimpleController(DamageTypeModel)\n"
  },
  {
    "path": "src/controllers/api/2024/equipmentCategoryController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport EquipmentCategory from '@/models/2024/equipmentCategory'\n\nexport default new SimpleController(EquipmentCategory)\n"
  },
  {
    "path": "src/controllers/api/2024/equipmentController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport Equipment from '@/models/2024/equipment'\n\nexport default new SimpleController(Equipment)\n"
  },
  {
    "path": "src/controllers/api/2024/featController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport FeatModel from '@/models/2024/feat'\n\nexport default new SimpleController(FeatModel)\n"
  },
  {
    "path": "src/controllers/api/2024/languageController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport LanguageModel from '@/models/2024/language'\n\nexport default new SimpleController(LanguageModel)\n"
  },
  {
    "path": "src/controllers/api/2024/magicItemController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport MagicItemModel from '@/models/2024/magicItem'\n\nexport default new SimpleController(MagicItemModel)\n"
  },
  {
    "path": "src/controllers/api/2024/magicSchoolController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport MagicSchoolModel from '@/models/2024/magicSchool'\n\nexport default new SimpleController(MagicSchoolModel)\n"
  },
  {
    "path": "src/controllers/api/2024/proficiencyController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport ProficiencyModel from '@/models/2024/proficiency'\n\nexport default new SimpleController(ProficiencyModel)\n"
  },
  {
    "path": "src/controllers/api/2024/skillController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport Skill from '@/models/2024/skill'\n\nexport default new SimpleController(Skill)\n"
  },
  {
    "path": "src/controllers/api/2024/speciesController.ts",
    "content": "import { NextFunction, Request, Response } from 'express'\n\nimport SimpleController from '@/controllers/simpleController'\nimport Species2024Model from '@/models/2024/species'\nimport Subspecies2024Model from '@/models/2024/subspecies'\nimport Trait2024Model from '@/models/2024/trait'\nimport { ShowParamsSchema } from '@/schemas/schemas'\nimport { ResourceList } from '@/util/data'\n\nconst simpleController = new SimpleController(Species2024Model)\n\nexport const index = async (req: Request, res: Response, next: NextFunction) =>\n  simpleController.index(req, res, next)\n\nexport const show = async (req: Request, res: Response, next: NextFunction) =>\n  simpleController.show(req, res, next)\n\nexport const showSubspeciesForSpecies = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    const urlString = '/api/2024/species/' + index\n\n    const data = await Subspecies2024Model.find({ 'species.url': urlString }).select({\n      index: 1,\n      name: 1,\n      url: 1,\n      _id: 0\n    })\n    return res.status(200).json(ResourceList(data))\n  } catch (err) {\n    next(err)\n  }\n}\n\nexport const showTraitsForSpecies = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    const urlString = '/api/2024/species/' + index\n\n    const data = await Trait2024Model.find({ 'species.url': urlString }).select({\n      index: 1,\n      name: 1,\n      url: 1,\n      _id: 0\n    })\n    return res.status(200).json(ResourceList(data))\n  } catch (err) {\n    next(err)\n  }\n}\n"
  },
  {
    "path": "src/controllers/api/2024/subclassController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport SubclassModel from '@/models/2024/subclass'\n\nexport default new SimpleController(SubclassModel)\n"
  },
  {
    "path": "src/controllers/api/2024/subspeciesController.ts",
    "content": "import { NextFunction, Request, Response } from 'express'\n\nimport SimpleController from '@/controllers/simpleController'\nimport Subspecies2024Model from '@/models/2024/subspecies'\nimport Trait2024Model from '@/models/2024/trait'\nimport { ShowParamsSchema } from '@/schemas/schemas'\nimport { ResourceList } from '@/util/data'\n\nconst simpleController = new SimpleController(Subspecies2024Model)\n\nexport const index = async (req: Request, res: Response, next: NextFunction) =>\n  simpleController.index(req, res, next)\n\nexport const show = async (req: Request, res: Response, next: NextFunction) =>\n  simpleController.show(req, res, next)\n\nexport const showTraitsForSubspecies = async (\n  req: Request,\n  res: Response,\n  next: NextFunction\n) => {\n  try {\n    const validatedParams = ShowParamsSchema.safeParse(req.params)\n    if (!validatedParams.success) {\n      return res\n        .status(400)\n        .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n    }\n    const { index } = validatedParams.data\n\n    const urlString = '/api/2024/subspecies/' + index\n\n    const data = await Trait2024Model.find({ 'subspecies.url': urlString }).select({\n      index: 1,\n      name: 1,\n      url: 1,\n      _id: 0\n    })\n    return res.status(200).json(ResourceList(data))\n  } catch (err) {\n    next(err)\n  }\n}\n"
  },
  {
    "path": "src/controllers/api/2024/traitController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport Trait2024Model from '@/models/2024/trait'\n\nexport default new SimpleController(Trait2024Model)\n"
  },
  {
    "path": "src/controllers/api/2024/weaponMasteryPropertyController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport WeaponMasteryPropertyModel from '@/models/2024/weaponMasteryProperty'\n\nexport default new SimpleController(WeaponMasteryPropertyModel)\n"
  },
  {
    "path": "src/controllers/api/2024/weaponPropertyController.ts",
    "content": "import SimpleController from '@/controllers/simpleController'\nimport WeaponPropertyModel from '@/models/2024/weaponProperty'\n\nexport default new SimpleController(WeaponPropertyModel)\n"
  },
  {
    "path": "src/controllers/api/imageController.ts",
    "content": "import { NextFunction, Request, Response } from 'express'\n\nimport { awsRegion } from '@/util/environmentVariables'\n\nconst BUCKET_NAME = 'dnd-5e-api-images'\nconst AWS_REGION = awsRegion || 'us-east-1'\n\nconst show = async (req: Request, res: Response, next: NextFunction) => {\n  let key: string | undefined\n  try {\n    key = req.url.slice(1)\n    if (!key || !/^[a-zA-Z0-9/._-]+$/.test(key)) {\n      return res.status(400).send('Invalid image path')\n    }\n\n    const publicUrl = `https://${BUCKET_NAME}.s3.${AWS_REGION}.amazonaws.com/${key}`\n    const s3Response = await fetch(publicUrl)\n\n    if (!s3Response.ok) {\n      return res.status(s3Response.status).send(await s3Response.text())\n    }\n\n    const contentTypeFromHeader = s3Response.headers.get('content-type')\n    const actualContentType =\n      contentTypeFromHeader != null ? contentTypeFromHeader : 'application/octet-stream'\n    res.setHeader('Content-Type', actualContentType)\n    const contentLength = s3Response.headers.get('content-length')\n    if (typeof contentLength === 'string' && contentLength) {\n      res.setHeader('Content-Length', contentLength)\n    }\n\n    if (s3Response.body) {\n      const reader = s3Response.body.getReader()\n      while (true) {\n        const { done, value } = await reader.read()\n        if (done) break\n        res.write(value)\n      }\n      res.end()\n    } else {\n      throw new Error('Response body from S3 was null')\n    }\n  } catch (err: any) {\n    if (err !== null) {\n      res.status(404).end('File Not Found')\n    } else {\n      console.error('Error fetching image from S3:', err)\n      next(err)\n    }\n  }\n}\n\nexport default {\n  show\n}\n"
  },
  {
    "path": "src/controllers/api/v2014Controller.ts",
    "content": "import { NextFunction, Request, Response } from 'express'\n\nimport Collection from '@/models/2014/collection'\n\nexport const index = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    const data = await Collection.find({})\n      .select({ index: 1, _id: 0 })\n      .sort({ index: 'asc' })\n      .exec()\n\n    const apiIndex: Record<string, string> = {}\n    data.forEach((item) => {\n      if (item.index === 'levels') return\n\n      apiIndex[item.index] = `/api/2014/${item.index}`\n    })\n\n    return res.status(200).json(apiIndex)\n  } catch (err) {\n    next(err)\n  }\n}\n"
  },
  {
    "path": "src/controllers/api/v2024Controller.ts",
    "content": "import { NextFunction, Request, Response } from 'express'\n\nimport Collection from '@/models/2024/collection'\n\nexport const index = async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    const data = await Collection.find({})\n      .select({ index: 1, _id: 0 })\n      .sort({ index: 'asc' })\n      .exec()\n\n    const apiIndex: Record<string, string> = {}\n    data.forEach((item) => {\n      if (item.index === 'levels') return\n\n      apiIndex[item.index] = `/api/2024/${item.index}`\n    })\n\n    return res.status(200).json(apiIndex)\n  } catch (err) {\n    next(err)\n  }\n}\n"
  },
  {
    "path": "src/controllers/apiController.ts",
    "content": "import { NextFunction, Request, Response } from 'express'\n\nimport Collection from '@/models/2014/collection'\n\nexport default async (req: Request, res: Response, next: NextFunction) => {\n  try {\n    const collections = await Collection.find({})\n\n    const colName = req.path.split('/')[1]\n    const colRequested = collections.find((col) => col.index === colName)\n\n    if (colRequested === undefined && colName !== '') {\n      res.sendStatus(404)\n      return\n    }\n\n    const queryString = req.originalUrl.split('?')?.[1]\n    const redirectUrl = '/api/2014' + req.path\n    const urlWithQuery = queryString === undefined ? redirectUrl : redirectUrl + '?' + queryString\n    res.redirect(301, urlWithQuery)\n  } catch (err) {\n    next(err)\n  }\n}\n"
  },
  {
    "path": "src/controllers/docsController.ts",
    "content": "import { Request, Response } from 'express'\n\nexport default (req: Request, res: Response) => {\n  res.redirect('https://5e-bits.github.io/docs')\n}\n"
  },
  {
    "path": "src/controllers/simpleController.ts",
    "content": "import { ReturnModelType } from '@typegoose/typegoose'\nimport { NextFunction, Request, Response } from 'express'\n\nimport { NameQuerySchema, ShowParamsSchema } from '@/schemas/schemas'\nimport { ResourceList } from '@/util/data'\nimport { escapeRegExp } from '@/util/regex'\n\ninterface IndexQuery {\n  name?: { $regex: RegExp }\n}\n\nclass SimpleController {\n  Schema: ReturnModelType<any>\n\n  constructor(Schema: ReturnModelType<any>) {\n    this.Schema = Schema\n  }\n\n  async index(req: Request, res: Response, next: NextFunction) {\n    try {\n      // Validate query parameters\n      const validatedQuery = NameQuerySchema.safeParse(req.query)\n\n      if (!validatedQuery.success) {\n        // Handle validation errors - customize error response as needed\n        return res\n          .status(400)\n          .json({ error: 'Invalid query parameters', details: validatedQuery.error.issues })\n      }\n\n      const { name } = validatedQuery.data\n\n      const searchQueries: IndexQuery = {}\n      if (name !== undefined) {\n        // Use validated name\n        searchQueries.name = { $regex: new RegExp(escapeRegExp(name), 'i') }\n      }\n\n      const data = await this.Schema.find(searchQueries)\n        .select({ index: 1, name: 1, url: 1, _id: 0 })\n        .sort({ index: 'asc' })\n        .exec()\n\n      return res.status(200).json(ResourceList(data))\n    } catch (err) {\n      next(err)\n    }\n  }\n\n  async show(req: Request, res: Response, next: NextFunction) {\n    try {\n      // Validate path parameters\n      const validatedParams = ShowParamsSchema.safeParse(req.params)\n\n      if (!validatedParams.success) {\n        // Handle validation errors\n        return res\n          .status(400)\n          .json({ error: 'Invalid path parameters', details: validatedParams.error.issues })\n      }\n\n      const { index } = validatedParams.data // Use validated index\n\n      // Use validated index in the query\n      const data = await this.Schema.findOne({ index })\n      if (data === null) return next()\n      res.status(200).json(data)\n    } catch (err) {\n      next(err)\n    }\n  }\n}\n\nexport default SimpleController\n"
  },
  {
    "path": "src/css/custom.css",
    "content": "/* Reset and base styles */\n* {\n  margin: 0;\n  padding: 0;\n  box-sizing: border-box;\n}\n\n:root {\n  --primary-color: #d81921;\n  --primary-dark: #c62828;\n  --text-color: #263238;\n  --bg-dark: #21201e;\n  --bg-light: #f5f5f5;\n  --link-color: #1b95e0;\n  --spacing: 1rem;\n}\n\nbody {\n  font-family: 'Helvetica', sans-serif;\n  color: var(--text-color);\n  line-height: 1.6;\n  padding-top: 3.5rem;\n}\n\n/* Typography */\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  margin: 0 0 var(--spacing) 0;\n  font-weight: 900;\n}\n\na {\n  color: var(--link-color);\n  text-decoration: none;\n}\n\n/* Navigation */\n.navbar {\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  background: var(--primary-color);\n  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n  z-index: 1000;\n  height: 3.5rem;\n  overflow: hidden;\n}\n\n.nav-container {\n  max-width: 1200px;\n  margin: 0 auto;\n  padding: 0 var(--spacing);\n}\n\n.nav-links {\n  display: flex;\n  justify-content: flex-end;\n  list-style: none;\n}\n\n.nav-links li a {\n  display: block;\n  padding: 1rem;\n  color: white;\n  transition: background-color 0.3s;\n}\n\n.nav-links li.active a,\n.nav-links li a:hover {\n  background-color: var(--primary-dark);\n}\n\n/* Header */\n.header {\n  background-color: var(--bg-dark);\n  padding: 4rem 1rem;\n  text-align: center;\n}\n\n.header h1 {\n  color: white;\n  margin: 0 auto;\n  max-width: 800px;\n  font-size: 3.5rem;\n  line-height: 1.2;\n}\n\n.header h2 {\n  color: white;\n  margin: 0.5rem auto 0;\n  max-width: 800px;\n  font-weight: 300;\n  font-size: 1.5rem;\n  opacity: 0.9;\n}\n\n/* CTA Section */\n.cta {\n  background-color: var(--bg-light);\n  padding: 3rem 1rem;\n  text-align: center;\n  font-size: 1.3rem;\n  font-weight: 300;\n}\n\n/* Interactive Section */\n.content {\n  max-width: 1200px;\n  margin: 0 auto;\n  padding: 2rem var(--spacing);\n}\n\n.interactive-section {\n  max-width: 800px;\n  margin: 0 auto;\n}\n\n.api-input {\n  display: flex;\n  margin: 1rem 0;\n  border: 1px solid #ddd;\n  border-radius: 4px;\n  overflow: hidden;\n}\n\n.api-prefix {\n  padding: 0.5rem 1rem;\n  background: var(--bg-light);\n  border-right: 1px solid #ddd;\n  white-space: nowrap;\n}\n\n#interactive {\n  flex: 1;\n  padding: 0.5rem;\n  border: none;\n  outline: none;\n}\n\n.api-input button {\n  padding: 0.5rem 1.5rem;\n  background: var(--link-color);\n  color: white;\n  border: none;\n  cursor: pointer;\n  transition: background-color 0.3s;\n}\n\n.api-input button:hover {\n  background-color: #1576b3;\n}\n\n.hints {\n  font-size: 0.9rem;\n  margin: 1rem 0;\n}\n\n.output {\n  background: var(--bg-light);\n  padding: 1rem;\n  border-radius: 4px;\n  overflow: auto;\n  max-height: 400px;\n  white-space: pre-wrap;\n  word-break: break-word;\n}\n\n/* Responsive Design */\n@media (max-width: 768px) {\n  .nav-links {\n    display: none;\n  }\n\n  .menu-toggle {\n    display: block;\n  }\n\n  .api-input {\n    flex-direction: column;\n  }\n\n  .api-prefix {\n    border-right: none;\n    border-bottom: 1px solid #ddd;\n  }\n}\n\n.menu-toggle {\n  display: none;\n  background: none;\n  border: none;\n  padding: 1rem;\n  cursor: pointer;\n}\n\n.menu-toggle span {\n  display: block;\n  width: 25px;\n  height: 3px;\n  background: white;\n  margin: 4px 0;\n  transition: 0.3s;\n}\n"
  },
  {
    "path": "src/graphql/2014/common/choiceTypes.ts",
    "content": "import { Field, Int, ObjectType } from 'type-graphql'\n\nimport { AbilityScore } from '@/models/2014/abilityScore'\nimport { Language } from '@/models/2014/language'\nimport { Proficiency } from '@/models/2014/proficiency'\n\nimport { ProficiencyChoiceItem } from './unions'\n\n// --- Language Choice Types ---\n@ObjectType({ description: 'Represents a reference to a language within a choice option set.' })\nexport class LanguageChoiceOption {\n  @Field(() => String, { description: 'The type of this option (e.g., \"reference\").' })\n  option_type!: string\n\n  @Field(() => Language, { description: 'The resolved Language object.' })\n  item!: Language\n}\n\n@ObjectType({ description: 'Represents a set of language options for a choice.' })\nexport class LanguageChoiceOptionSet {\n  @Field(() => String, {\n    description: 'The type of the option set (e.g., resource_list, options_array).'\n  })\n  option_set_type!: string\n\n  @Field(() => [LanguageChoiceOption], {\n    description: 'The list of language options available.'\n  })\n  options!: LanguageChoiceOption[]\n}\n\n@ObjectType({ description: 'Represents a choice from a list of languages.' })\nexport class LanguageChoice {\n  @Field(() => Int, { description: 'The number of languages to choose from this list.' })\n  choose!: number\n\n  @Field(() => String, { description: 'The type of choice (e.g., languages).' })\n  type!: string\n\n  @Field(() => LanguageChoiceOptionSet, { description: 'The set of language options available.' })\n  from!: LanguageChoiceOptionSet\n}\n\n// --- Proficiency Choice Types ---\n@ObjectType({\n  description:\n    'Represents a reference to a Proficiency or nested ProficiencyChoice within a choice option set.'\n})\nexport class ProficiencyChoiceOption {\n  @Field(() => String, { description: 'The type of this option (e.g., \"reference\", \"choice\").' })\n  option_type!: string\n\n  @Field(() => ProficiencyChoiceItem, {\n    description: 'The resolved Proficiency object or nested ProficiencyChoice.'\n  })\n  item!: Proficiency | ProficiencyChoice\n}\n\n@ObjectType({ description: 'Represents a set of Proficiency options for a choice.' })\nexport class ProficiencyChoiceOptionSet {\n  @Field(() => String, {\n    description: 'The type of the option set (e.g., resource_list, options_array).'\n  })\n  option_set_type!: string\n\n  @Field(() => [ProficiencyChoiceOption], {\n    description: 'The list of Proficiency options available.'\n  })\n  options!: ProficiencyChoiceOption[]\n}\n\n@ObjectType({\n  description: 'Represents a choice from a list of Proficiencies or nested ProficiencyChoices.'\n})\nexport class ProficiencyChoice {\n  @Field(() => Int, { description: 'The number of Proficiencies to choose from this list.' })\n  choose!: number\n\n  @Field(() => String, { description: 'The type of choice (e.g., proficiencies).' })\n  type!: string\n\n  @Field(() => ProficiencyChoiceOptionSet, {\n    description: 'The set of Proficiency options available.'\n  })\n  from!: ProficiencyChoiceOptionSet\n\n  @Field(() => String, { nullable: true, description: 'Description of the choice.' })\n  desc?: string\n}\n\n// --- Prerequisite Choice Types ---\n@ObjectType({ description: 'A single prerequisite option' })\nexport class PrerequisiteChoiceOption {\n  @Field(() => String, { description: 'The type of option.' })\n  option_type!: string\n\n  @Field(() => AbilityScore, { description: 'The ability score required.' })\n  ability_score!: AbilityScore\n\n  @Field(() => Int, { description: 'The minimum score required.' })\n  minimum_score!: number\n}\n\n@ObjectType({ description: 'A set of prerequisite options to choose from' })\nexport class PrerequisiteChoiceOptionSet {\n  @Field(() => String, { description: 'The type of option set.' })\n  option_set_type!: string\n\n  @Field(() => [PrerequisiteChoiceOption], { description: 'The available options.' })\n  options!: PrerequisiteChoiceOption[]\n}\n\n@ObjectType({ description: 'A choice of prerequisites for multi-classing' })\nexport class PrerequisiteChoice {\n  @Field(() => Int, { description: 'Number of prerequisites to choose.' })\n  choose!: number\n\n  @Field(() => String, { description: 'Type of prerequisites to choose from.' })\n  type!: string\n\n  @Field(() => PrerequisiteChoiceOptionSet, { description: 'The options to choose from.' })\n  from!: PrerequisiteChoiceOptionSet\n\n  @Field(() => String, { nullable: true, description: 'Description of the prerequisite choice.' })\n  desc?: string\n}\n\n// --- Ability Score Bonus Choice Types ---\n@ObjectType({ description: 'A single ability score bonus option' })\nexport class AbilityScoreBonusChoiceOption {\n  @Field(() => String, { description: 'The type of option.' })\n  option_type!: string\n\n  @Field(() => AbilityScore, { description: 'The ability score to increase.' })\n  ability_score!: AbilityScore\n\n  @Field(() => Int, { description: 'The amount to increase the ability score by.' })\n  bonus!: number\n}\n\n@ObjectType({ description: 'A set of ability score bonus options to choose from' })\nexport class AbilityScoreBonusChoiceOptionSet {\n  @Field(() => String, { description: 'The type of option set.' })\n  option_set_type!: string\n\n  @Field(() => [AbilityScoreBonusChoiceOption], { description: 'The available options.' })\n  options!: AbilityScoreBonusChoiceOption[]\n}\n\n@ObjectType({ description: 'A choice of ability score bonuses for a race' })\nexport class AbilityScoreBonusChoice {\n  @Field(() => Int, { description: 'Number of ability score bonuses to choose.' })\n  choose!: number\n\n  @Field(() => String, { description: 'Type of ability score bonuses to choose from.' })\n  type!: string\n\n  @Field(() => AbilityScoreBonusChoiceOptionSet, { description: 'The options to choose from.' })\n  from!: AbilityScoreBonusChoiceOptionSet\n\n  @Field(() => String, {\n    nullable: true,\n    description: 'Description of the ability score bonus choice.'\n  })\n  desc?: string\n}\n"
  },
  {
    "path": "src/graphql/2014/common/equipmentTypes.ts",
    "content": "import { Field, Int, ObjectType } from 'type-graphql'\n\nimport { ArmorClass, Content, Equipment, Range, Speed, ThrowRange } from '@/models/2014/equipment'\nimport { WeaponProperty } from '@/models/2014/weaponProperty'\nimport { APIReference } from '@/models/common/apiReference'\nimport { Damage } from '@/models/common/damage'\n\nimport { IEquipment } from './interfaces'\n\n@ObjectType({ description: 'Represents Armor equipment', implements: IEquipment })\nexport class Armor extends Equipment {\n  @Field(() => String, { description: 'Category of armor (e.g., Light, Medium, Heavy).' })\n  declare armor_category: string\n\n  @Field(() => ArmorClass, { description: 'Armor export Class details for this armor.' })\n  declare armor_class: ArmorClass\n\n  @Field(() => Int, {\n    nullable: true,\n    description: 'Minimum Strength score required to use this armor effectively.'\n  })\n  declare str_minimum?: number\n\n  @Field(() => Boolean, {\n    nullable: true,\n    description: 'Whether wearing the armor imposes disadvantage on Stealth checks.'\n  })\n  declare stealth_disadvantage?: boolean\n}\n\n@ObjectType({ description: 'Represents Weapon equipment', implements: IEquipment })\nexport class Weapon extends Equipment {\n  @Field(() => String, { description: 'Category of weapon (e.g., Simple, Martial).' })\n  declare weapon_category: string\n\n  @Field(() => String, { description: 'Range classification of weapon (e.g., Melee, Ranged).' })\n  declare weapon_range: string\n\n  @Field(() => String, { description: 'Range category for weapons (e.g., Melee, Ranged).' })\n  declare category_range: string\n\n  @Field(() => Damage, { nullable: true, description: 'Primary damage dealt by the weapon.' })\n  declare damage?: Damage\n\n  @Field(() => Damage, {\n    nullable: true,\n    description: 'Damage dealt when using the weapon with two hands.'\n  })\n  declare two_handed_damage?: Damage\n\n  @Field(() => Range, { nullable: true, description: 'Weapon range details.' })\n  declare range?: Range\n\n  @Field(() => ThrowRange, { nullable: true, description: 'Range when the weapon is thrown.' })\n  declare throw_range?: ThrowRange\n\n  @Field(() => [WeaponProperty], { nullable: true, description: 'Properties of the weapon.' })\n  declare properties?: APIReference[] // Resolved externally\n}\n\n@ObjectType({ description: 'Represents Tool equipment', implements: IEquipment })\nexport class Tool extends Equipment {\n  @Field(() => String, { description: \"Category of tool (e.g., Artisan's Tools, Gaming Set).\" })\n  declare tool_category: string\n}\n\n@ObjectType({ description: 'Represents Gear equipment (general purpose)', implements: IEquipment })\nexport class Gear extends Equipment {}\n\n@ObjectType({\n  description: \"Represents Gear that contains other items (e.g., Explorer's Pack)\",\n  implements: IEquipment\n})\nexport class Pack extends Gear {\n  @Field(() => [Content], { nullable: true, description: 'Items contained within the pack.' })\n  declare contents?: Content[]\n}\n\n@ObjectType({ description: 'Represents Ammunition equipment', implements: IEquipment })\nexport class Ammunition extends Gear {\n  @Field(() => Int, { description: 'Quantity of ammunition in the bundle.' })\n  declare quantity: number\n}\n\n@ObjectType({ description: 'Represents Vehicle equipment', implements: IEquipment })\nexport class Vehicle extends Equipment {\n  @Field(() => String, { description: 'Category of vehicle (e.g., Ship, Land).' })\n  declare vehicle_category: string\n\n  @Field(() => Speed, { nullable: true, description: 'Movement speed of the vehicle.' })\n  declare speed?: Speed\n\n  @Field(() => String, { nullable: true, description: 'Carrying capacity of the vehicle.' })\n  declare capacity?: string\n}\n"
  },
  {
    "path": "src/graphql/2014/common/interfaces.ts",
    "content": "import { Field, Float, InterfaceType } from 'type-graphql'\n\nimport { Cost } from '@/models/2014/equipment'\n\n@InterfaceType({\n  description: 'Common fields shared by all types of equipment and magic items.'\n})\nexport abstract class IEquipment {\n  @Field(() => String, { description: 'The unique identifier for this equipment.' })\n  index!: string\n\n  @Field(() => String, { description: 'The name of the equipment.' })\n  name!: string\n\n  @Field(() => Cost, { description: 'Cost of the equipment in coinage.' })\n  cost!: Cost\n\n  @Field(() => Float, { nullable: true, description: 'Weight of the equipment in pounds.' })\n  weight?: number\n\n  @Field(() => [String], { nullable: true, description: 'Description of the equipment.' })\n  desc?: string[]\n}\n"
  },
  {
    "path": "src/graphql/2014/common/unions.ts",
    "content": "import { createUnionType } from 'type-graphql'\n\nimport { ProficiencyChoice } from '@/graphql/2014/common/choiceTypes'\nimport { MagicItem } from '@/models/2014/magicItem'\nimport { Proficiency } from '@/models/2014/proficiency'\n\nimport { Ammunition, Armor, Gear, Pack, Tool, Vehicle, Weapon } from './equipmentTypes'\n\nfunction resolveEquipmentType(\n  value: any\n):\n  | typeof Armor\n  | typeof Weapon\n  | typeof Tool\n  | typeof Gear\n  | typeof Pack\n  | typeof Ammunition\n  | typeof Vehicle\n  | null {\n  if ('armor_class' in value) {\n    return Armor\n  }\n  if ('weapon_category' in value || 'weapon_range' in value) {\n    return Weapon\n  }\n  if ('tool_category' in value) {\n    return Tool\n  }\n  if ('vehicle_category' in value) {\n    return Vehicle\n  }\n  if ('contents' in value) {\n    return Pack\n  }\n  if (value.gear_category?.index === 'ammunition') {\n    return Ammunition\n  }\n  if ('gear_category' in value) {\n    return Gear\n  }\n  return null\n}\n\nexport const EquipmentOrMagicItem = createUnionType({\n  name: 'EquipmentOrMagicItem',\n  types: () => {\n    return [Armor, Weapon, Tool, Gear, Pack, Ammunition, Vehicle, MagicItem] as const\n  },\n  resolveType: (value) => {\n    if ('rarity' in value) {\n      return MagicItem\n    }\n\n    const equipmentType = resolveEquipmentType(value)\n    if (equipmentType) {\n      return equipmentType\n    }\n\n    console.warn('Could not resolve type for EquipmentOrMagicItem:', value)\n    throw new Error('Could not resolve type for EquipmentOrMagicItem')\n  }\n})\n\nexport const AnyEquipment = createUnionType({\n  name: 'AnyEquipment',\n  types: () => {\n    return [Armor, Weapon, Tool, Gear, Pack, Ammunition, Vehicle] as const\n  },\n  resolveType: (value) => {\n    const equipmentType = resolveEquipmentType(value)\n    if (equipmentType) {\n      return equipmentType\n    }\n\n    console.warn('Could not resolve type for AnyEquipment:', value)\n    return Gear\n  }\n})\n\nexport const ProficiencyChoiceItem = createUnionType({\n  name: 'ProficiencyChoiceItem',\n  types: () => [Proficiency, ProficiencyChoice] as const,\n  resolveType: (value) => {\n    if (typeof value === 'object' && 'choose' in value && 'type' in value && 'from' in value) {\n      return ProficiencyChoice\n    }\n    return Proficiency\n  }\n})\n"
  },
  {
    "path": "src/graphql/2014/resolvers/abilityScore/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum AbilityScoreOrderField {\n  NAME = 'name',\n  FULL_NAME = 'full_name'\n}\n\nexport const ABILITY_SCORE_SORT_FIELD_MAP: Record<AbilityScoreOrderField, string> = {\n  [AbilityScoreOrderField.NAME]: 'name',\n  [AbilityScoreOrderField.FULL_NAME]: 'full_name'\n}\n\nregisterEnumType(AbilityScoreOrderField, {\n  name: 'AbilityScoreOrderField',\n  description: 'Fields to sort Ability Scores by'\n})\n\n@InputType()\nexport class AbilityScoreOrder implements BaseOrderInterface<AbilityScoreOrderField> {\n  @Field(() => AbilityScoreOrderField)\n  by!: AbilityScoreOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => AbilityScoreOrder, { nullable: true })\n  then_by?: AbilityScoreOrder\n}\n\nexport const AbilityScoreOrderSchema: z.ZodType<AbilityScoreOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(AbilityScoreOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: AbilityScoreOrderSchema.optional()\n  })\n)\n\nexport const AbilityScoreArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  full_name: z.string().optional(),\n  order: AbilityScoreOrderSchema.optional()\n})\n\nexport const AbilityScoreIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class AbilityScoreArgs extends BaseFilterArgs {\n  @Field(() => String, {\n    nullable: true,\n    description: 'Filter by ability score full name (case-insensitive, partial match)'\n  })\n  full_name?: string\n\n  @Field(() => AbilityScoreOrder, {\n    nullable: true,\n    description: 'Specify sorting order for ability scores.'\n  })\n  order?: AbilityScoreOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/abilityScore/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { resolveMultipleReferences } from '@/graphql/utils/resolvers'\nimport AbilityScoreModel, { AbilityScore } from '@/models/2014/abilityScore'\nimport SkillModel, { Skill } from '@/models/2014/skill'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  ABILITY_SCORE_SORT_FIELD_MAP,\n  AbilityScoreArgs,\n  AbilityScoreArgsSchema,\n  AbilityScoreIndexArgsSchema,\n  AbilityScoreOrderField\n} from './args'\n\n@Resolver(AbilityScore)\nexport class AbilityScoreResolver {\n  @Query(() => [AbilityScore], {\n    description: 'Gets all ability scores, optionally filtered by name and sorted.'\n  })\n  async abilityScores(\n    @Args(() => AbilityScoreArgs) args: AbilityScoreArgs\n  ): Promise<AbilityScore[]> {\n    const validatedArgs = AbilityScoreArgsSchema.parse(args)\n\n    const query = AbilityScoreModel.find()\n    const filters: Record<string, any>[] = []\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      filters.push({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    if (validatedArgs.full_name != null && validatedArgs.full_name !== '') {\n      filters.push({\n        full_name: { $regex: new RegExp(escapeRegExp(validatedArgs.full_name), 'i') }\n      })\n    }\n\n    if (filters.length > 0) {\n      query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<AbilityScoreOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: ABILITY_SCORE_SORT_FIELD_MAP,\n      defaultSortField: AbilityScoreOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => AbilityScore, {\n    nullable: true,\n    description: 'Gets a single ability score by index.'\n  })\n  async abilityScore(@Arg('index', () => String) indexInput: string): Promise<AbilityScore | null> {\n    const { index } = AbilityScoreIndexArgsSchema.parse({ index: indexInput })\n    return AbilityScoreModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => [Skill])\n  async skills(@Root() abilityScore: AbilityScore): Promise<Skill[]> {\n    return resolveMultipleReferences(abilityScore.skills, SkillModel)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/alignment/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum AlignmentOrderField {\n  NAME = 'name'\n}\n\nexport const ALIGNMENT_SORT_FIELD_MAP: Record<AlignmentOrderField, string> = {\n  [AlignmentOrderField.NAME]: 'name'\n}\n\nregisterEnumType(AlignmentOrderField, {\n  name: 'AlignmentOrderField',\n  description: 'Fields to sort Alignments by'\n})\n\n@InputType()\nexport class AlignmentOrder implements BaseOrderInterface<AlignmentOrderField> {\n  @Field(() => AlignmentOrderField)\n  by!: AlignmentOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => AlignmentOrder, { nullable: true })\n  then_by?: AlignmentOrder\n}\n\nexport const AlignmentOrderSchema: z.ZodType<AlignmentOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(AlignmentOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: AlignmentOrderSchema.optional()\n  })\n)\n\nexport const AlignmentArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: AlignmentOrderSchema.optional()\n})\n\nexport const AlignmentIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class AlignmentArgs extends BaseFilterArgs {\n  @Field(() => AlignmentOrder, {\n    nullable: true,\n    description: 'Specify sorting order for alignments.'\n  })\n  order?: AlignmentOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/alignment/resolver.ts",
    "content": "import { Arg, Args, Query, Resolver } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport AlignmentModel, { Alignment } from '@/models/2014/alignment'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  ALIGNMENT_SORT_FIELD_MAP,\n  AlignmentArgs,\n  AlignmentArgsSchema,\n  AlignmentIndexArgsSchema,\n  AlignmentOrderField\n} from './args'\n\n@Resolver(Alignment)\nexport class AlignmentResolver {\n  @Query(() => [Alignment], {\n    description: 'Gets all alignments, optionally filtered by name and sorted.'\n  })\n  async alignments(@Args(() => AlignmentArgs) args: AlignmentArgs): Promise<Alignment[]> {\n    const validatedArgs = AlignmentArgsSchema.parse(args)\n\n    const query = AlignmentModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<AlignmentOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: ALIGNMENT_SORT_FIELD_MAP,\n      defaultSortField: AlignmentOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Alignment, { nullable: true, description: 'Gets a single alignment by index.' })\n  async alignment(@Arg('index', () => String) indexInput: string): Promise<Alignment | null> {\n    const { index } = AlignmentIndexArgsSchema.parse({ index: indexInput })\n    return AlignmentModel.findOne({ index }).lean()\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/background/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum BackgroundOrderField {\n  NAME = 'name'\n}\n\nexport const BACKGROUND_SORT_FIELD_MAP: Record<BackgroundOrderField, string> = {\n  [BackgroundOrderField.NAME]: 'name'\n}\n\nregisterEnumType(BackgroundOrderField, {\n  name: 'BackgroundOrderField',\n  description: 'Fields to sort Backgrounds by'\n})\n\n@InputType()\nexport class BackgroundOrder implements BaseOrderInterface<BackgroundOrderField> {\n  @Field(() => BackgroundOrderField)\n  by!: BackgroundOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => BackgroundOrder, { nullable: true })\n  then_by?: BackgroundOrder\n}\n\nexport const BackgroundOrderSchema: z.ZodType<BackgroundOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(BackgroundOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: BackgroundOrderSchema.optional()\n  })\n)\n\nexport const BackgroundArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: BackgroundOrderSchema.optional()\n})\n\nexport const BackgroundIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class BackgroundArgs extends BaseFilterArgs {\n  @Field(() => BackgroundOrder, {\n    nullable: true,\n    description: 'Specify sorting order for backgrounds.'\n  })\n  order?: BackgroundOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/background/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { LanguageChoice } from '@/graphql/2014/common/choiceTypes'\nimport {\n  IdealChoice,\n  IdealOption as ResolvedIdealOption\n} from '@/graphql/2014/types/backgroundTypes'\nimport { StartingEquipmentChoice } from '@/graphql/2014/types/startingEquipment'\nimport { resolveLanguageChoice } from '@/graphql/2014/utils/resolvers'\nimport { resolveStartingEquipmentChoices } from '@/graphql/2014/utils/startingEquipmentResolver'\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { StringChoice } from '@/graphql/common/choiceTypes'\nimport {\n  resolveMultipleReferences,\n  resolveSingleReference,\n  resolveStringChoice\n} from '@/graphql/utils/resolvers'\nimport AlignmentModel, { Alignment } from '@/models/2014/alignment'\nimport BackgroundModel, { Background, EquipmentRef } from '@/models/2014/background'\nimport EquipmentModel, { Equipment } from '@/models/2014/equipment'\nimport ProficiencyModel, { Proficiency } from '@/models/2014/proficiency'\nimport { Choice, IdealOption, OptionsArrayOptionSet } from '@/models/common/choice'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  BACKGROUND_SORT_FIELD_MAP,\n  BackgroundArgs,\n  BackgroundArgsSchema,\n  BackgroundIndexArgsSchema,\n  BackgroundOrderField\n} from './args'\n\n@Resolver(Background)\nexport class BackgroundResolver {\n  @Query(() => [Background], {\n    description: 'Gets all backgrounds, optionally filtered by name and sorted by name.'\n  })\n  async backgrounds(@Args(() => BackgroundArgs) args: BackgroundArgs): Promise<Background[]> {\n    const validatedArgs = BackgroundArgsSchema.parse(args)\n    const query = BackgroundModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<BackgroundOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: BACKGROUND_SORT_FIELD_MAP,\n      defaultSortField: BackgroundOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Background, { nullable: true, description: 'Gets a single background by index.' })\n  async background(@Arg('index', () => String) indexInput: string): Promise<Background | null> {\n    const { index } = BackgroundIndexArgsSchema.parse({ index: indexInput })\n    return BackgroundModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => [Proficiency], { nullable: true })\n  async starting_proficiencies(@Root() background: Background): Promise<Proficiency[]> {\n    return resolveMultipleReferences(background.starting_proficiencies, ProficiencyModel)\n  }\n\n  @FieldResolver(() => StringChoice, {\n    nullable: true,\n    description: 'Resolves the flaws choice for the background.'\n  })\n  async flaws(@Root() background: Background): Promise<StringChoice | null> {\n    return resolveStringChoice(background.flaws as Choice)\n  }\n\n  @FieldResolver(() => StringChoice, {\n    nullable: true,\n    description: 'Resolves the bonds choice for the background.'\n  })\n  async bonds(@Root() background: Background): Promise<StringChoice | null> {\n    return resolveStringChoice(background.bonds as Choice)\n  }\n\n  @FieldResolver(() => StringChoice, {\n    nullable: true,\n    description: 'Resolves the personality traits choice for the background.'\n  })\n  async personality_traits(@Root() background: Background): Promise<StringChoice | null> {\n    return resolveStringChoice(background.personality_traits as Choice)\n  }\n\n  @FieldResolver(() => IdealChoice, {\n    nullable: true,\n    description: 'Resolves the ideals choice for the background.'\n  })\n  async ideals(@Root() background: Background): Promise<IdealChoice> {\n    const choiceData = background.ideals as Choice\n\n    const optionSet = choiceData.from as OptionsArrayOptionSet\n\n    const resolvedIdealOptions: ResolvedIdealOption[] = []\n    if (Array.isArray(optionSet.options)) {\n      for (const option of optionSet.options) {\n        const idealOption = option as IdealOption\n        const resolvedAlignments = (await resolveMultipleReferences(\n          idealOption.alignments,\n          AlignmentModel\n        )) as Alignment[]\n\n        resolvedIdealOptions.push({\n          option_type: idealOption.option_type,\n          desc: idealOption.desc,\n          alignments: resolvedAlignments\n        })\n      }\n    }\n\n    return {\n      choose: choiceData.choose,\n      type: choiceData.type,\n      from: {\n        option_set_type: optionSet.option_set_type,\n        options: resolvedIdealOptions\n      }\n    }\n  }\n\n  @FieldResolver(() => LanguageChoice, {\n    nullable: true,\n    description: 'Resolves the language choices for the background.'\n  })\n  async language_options(@Root() background: Background): Promise<LanguageChoice | null> {\n    return resolveLanguageChoice(background.language_options as Choice)\n  }\n\n  @FieldResolver(() => [StartingEquipmentChoice], {\n    nullable: true,\n    description: 'Resolves starting equipment choices for the background.'\n  })\n  async starting_equipment_options(\n    @Root() background: Background\n  ): Promise<StartingEquipmentChoice[] | null> {\n    return resolveStartingEquipmentChoices(background.starting_equipment_options)\n  }\n}\n\n@Resolver(EquipmentRef)\nexport class EquipmentRefResolver {\n  @FieldResolver(() => Equipment)\n  async equipment(@Root() equipmentRef: EquipmentRef): Promise<Equipment | null> {\n    return resolveSingleReference(equipmentRef.equipment, EquipmentModel)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/class/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\nimport { NumberFilterInput, NumberFilterInputSchema } from '@/graphql/common/inputs'\n\nexport enum ClassOrderField {\n  NAME = 'name',\n  HIT_DIE = 'hit_die'\n}\n\nexport const CLASS_SORT_FIELD_MAP: Record<ClassOrderField, string> = {\n  [ClassOrderField.NAME]: 'name',\n  [ClassOrderField.HIT_DIE]: 'hit_die'\n}\n\nregisterEnumType(ClassOrderField, {\n  name: 'ClassOrderField',\n  description: 'Fields to sort Classes by'\n})\n\n@InputType()\nexport class ClassOrder implements BaseOrderInterface<ClassOrderField> {\n  @Field(() => ClassOrderField)\n  by!: ClassOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => ClassOrder, { nullable: true })\n  then_by?: ClassOrder\n}\n\nexport const ClassOrderSchema: z.ZodType<ClassOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(ClassOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: ClassOrderSchema.optional()\n  })\n)\n\nexport const ClassArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  hit_die: NumberFilterInputSchema.optional(),\n  order: ClassOrderSchema.optional()\n})\n\nexport const ClassIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class ClassArgs extends BaseFilterArgs {\n  @Field(() => NumberFilterInput, {\n    nullable: true,\n    description: 'Filter by hit die size. Allows exact match, list of values, or a range.'\n  })\n  hit_die?: NumberFilterInput\n\n  @Field(() => ClassOrder, {\n    nullable: true,\n    description: 'Specify sorting order for classes. Allows nested sorting.'\n  })\n  order?: ClassOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/class/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport {\n  PrerequisiteChoice,\n  PrerequisiteChoiceOption,\n  PrerequisiteChoiceOptionSet,\n  ProficiencyChoice\n} from '@/graphql/2014/common/choiceTypes'\nimport { AnyEquipment } from '@/graphql/2014/common/unions'\nimport { StartingEquipmentChoice } from '@/graphql/2014/types/startingEquipment'\nimport { resolveProficiencyChoiceArray } from '@/graphql/2014/utils/resolvers'\nimport { resolveStartingEquipmentChoices } from '@/graphql/2014/utils/startingEquipmentResolver'\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { buildMongoQueryFromNumberFilter } from '@/graphql/common/inputs'\nimport { resolveMultipleReferences, resolveSingleReference } from '@/graphql/utils/resolvers'\nimport AbilityScoreModel, { AbilityScore } from '@/models/2014/abilityScore'\nimport ClassModel, {\n  Class,\n  ClassEquipment,\n  MultiClassing,\n  MultiClassingPrereq\n} from '@/models/2014/class'\nimport EquipmentModel from '@/models/2014/equipment'\nimport LevelModel, { Level } from '@/models/2014/level'\nimport ProficiencyModel, { Proficiency } from '@/models/2014/proficiency'\nimport SpellModel, { Spell } from '@/models/2014/spell'\nimport SubclassModel, { Subclass } from '@/models/2014/subclass'\nimport { APIReference } from '@/models/common/apiReference'\nimport { Choice, OptionsArrayOptionSet, ScorePrerequisiteOption } from '@/models/common/choice'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  CLASS_SORT_FIELD_MAP,\n  ClassArgs,\n  ClassArgsSchema,\n  ClassIndexArgsSchema,\n  ClassOrderField\n} from './args'\n\n@Resolver(Class)\nexport class ClassResolver {\n  @Query(() => [Class], {\n    description: 'Gets all classes, optionally filtering by name or hit die and sorted.'\n  })\n  async classes(@Args(() => ClassArgs) args: ClassArgs): Promise<Class[]> {\n    const validatedArgs = ClassArgsSchema.parse(args)\n\n    const query = ClassModel.find()\n    const filters: any[] = []\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      filters.push({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    if (validatedArgs.hit_die) {\n      const hitDieQuery = buildMongoQueryFromNumberFilter(validatedArgs.hit_die)\n      if (hitDieQuery) {\n        filters.push({ hit_die: hitDieQuery })\n      }\n    }\n\n    if (filters.length > 0) {\n      query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<ClassOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: CLASS_SORT_FIELD_MAP,\n      defaultSortField: ClassOrderField.NAME\n    })\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Class, { nullable: true, description: 'Gets a single class by its index.' })\n  async class(@Arg('index', () => String) indexInput: string): Promise<Class | null> {\n    const { index } = ClassIndexArgsSchema.parse({ index: indexInput })\n    return ClassModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => [Level])\n  async class_levels(@Root() classData: Class): Promise<Level[]> {\n    return LevelModel.find({\n      'class.index': classData.index,\n      subclass: { $exists: false }\n    })\n      .sort({ level: 1 })\n      .lean()\n  }\n\n  @FieldResolver(() => [Proficiency])\n  async proficiencies(@Root() classData: Class): Promise<APIReference[]> {\n    return resolveMultipleReferences(classData.proficiencies, ProficiencyModel)\n  }\n\n  @FieldResolver(() => [AbilityScore])\n  async saving_throws(@Root() classData: Class): Promise<APIReference[]> {\n    return resolveMultipleReferences(classData.saving_throws, AbilityScoreModel)\n  }\n\n  @FieldResolver(() => [Subclass])\n  async subclasses(@Root() classData: Class): Promise<APIReference[]> {\n    return resolveMultipleReferences(classData.subclasses, SubclassModel)\n  }\n\n  @FieldResolver(() => [Spell])\n  async spells(@Root() classData: Class): Promise<Spell[]> {\n    return SpellModel.find({ 'classes.index': classData.index }).sort({ level: 1, name: 1 }).lean()\n  }\n\n  @FieldResolver(() => [ProficiencyChoice])\n  async proficiency_choices(@Root() classData: Class): Promise<ProficiencyChoice[]> {\n    return resolveProficiencyChoiceArray(classData.proficiency_choices)\n  }\n\n  @FieldResolver(() => [StartingEquipmentChoice], {\n    nullable: true,\n    description: 'Resolves starting equipment choices for the class.'\n  })\n  async starting_equipment_options(\n    @Root() classData: Class\n  ): Promise<StartingEquipmentChoice[] | null> {\n    return resolveStartingEquipmentChoices(classData.starting_equipment_options)\n  }\n}\n\n@Resolver(MultiClassing)\nexport class MultiClassingResolver {\n  @FieldResolver(() => [Proficiency])\n  async proficiencies(@Root() multiClassing: MultiClassing): Promise<APIReference[]> {\n    return resolveMultipleReferences(multiClassing.proficiencies, ProficiencyModel)\n  }\n\n  @FieldResolver(() => [ProficiencyChoice])\n  async proficiency_choices(@Root() multiClassing: MultiClassing): Promise<ProficiencyChoice[]> {\n    return resolveProficiencyChoiceArray(multiClassing.proficiency_choices)\n  }\n\n  @FieldResolver(() => PrerequisiteChoice)\n  async prerequisite_options(\n    @Root() multiClassing: MultiClassing\n  ): Promise<PrerequisiteChoice | null> {\n    return resolvePrerequisiteChoice(multiClassing.prerequisite_options)\n  }\n}\n\n@Resolver(MultiClassingPrereq)\nexport class MultiClassingPrereqResolver {\n  @FieldResolver(() => AbilityScore)\n  async ability_score(@Root() prerequisite: MultiClassingPrereq): Promise<APIReference | null> {\n    return resolveSingleReference(prerequisite.ability_score, AbilityScoreModel)\n  }\n}\n\n@Resolver(ClassEquipment)\nexport class ClassEquipmentResolver {\n  @FieldResolver(() => AnyEquipment, { nullable: true })\n  async equipment(@Root() classEquipment: ClassEquipment): Promise<typeof AnyEquipment | null> {\n    return resolveSingleReference(classEquipment.equipment, EquipmentModel)\n  }\n}\n\nasync function resolvePrerequisiteChoice(\n  choiceData: Choice | undefined | null\n): Promise<PrerequisiteChoice | null> {\n  if (!choiceData || !choiceData.type || typeof choiceData.choose !== 'number') {\n    return null\n  }\n\n  const gqlEmbeddedOptions: PrerequisiteChoiceOption[] = []\n\n  const optionsArraySet = choiceData.from as OptionsArrayOptionSet\n  for (const opt of optionsArraySet.options) {\n    if (opt.option_type === 'score_prerequisite') {\n      const scoreOpt = opt as ScorePrerequisiteOption\n      const abilityScore = await resolveSingleReference(scoreOpt.ability_score, AbilityScoreModel)\n      if (abilityScore != null) {\n        gqlEmbeddedOptions.push({\n          option_type: scoreOpt.option_type,\n          ability_score: abilityScore as AbilityScore,\n          minimum_score: scoreOpt.minimum_score\n        })\n      }\n    }\n  }\n\n  if (gqlEmbeddedOptions.length === 0 && optionsArraySet.options.length > 0) {\n    return null\n  }\n\n  const gqlOptionSet: PrerequisiteChoiceOptionSet = {\n    option_set_type: choiceData.from.option_set_type,\n    options: gqlEmbeddedOptions\n  }\n\n  return {\n    choose: choiceData.choose,\n    type: choiceData.type,\n    desc: choiceData.desc,\n    from: gqlOptionSet\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/condition/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum ConditionOrderField {\n  NAME = 'name'\n}\n\nexport const CONDITION_SORT_FIELD_MAP: Record<ConditionOrderField, string> = {\n  [ConditionOrderField.NAME]: 'name'\n}\n\nregisterEnumType(ConditionOrderField, {\n  name: 'ConditionOrderField',\n  description: 'Fields to sort Conditions by'\n})\n\n@InputType()\nexport class ConditionOrder implements BaseOrderInterface<ConditionOrderField> {\n  @Field(() => ConditionOrderField)\n  by!: ConditionOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => ConditionOrder, { nullable: true })\n  then_by?: ConditionOrder\n}\n\nexport const ConditionOrderSchema: z.ZodType<ConditionOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(ConditionOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: ConditionOrderSchema.optional()\n  })\n)\n\nexport const ConditionArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: ConditionOrderSchema.optional()\n})\n\nexport const ConditionIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class ConditionArgs extends BaseFilterArgs {\n  @Field(() => ConditionOrder, {\n    nullable: true,\n    description: 'Specify sorting order for conditions.'\n  })\n  order?: ConditionOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/condition/resolver.ts",
    "content": "import { Arg, Args, Query, Resolver } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport ConditionModel, { Condition } from '@/models/2014/condition'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  CONDITION_SORT_FIELD_MAP,\n  ConditionArgs,\n  ConditionArgsSchema,\n  ConditionIndexArgsSchema,\n  ConditionOrderField\n} from './args'\n\n@Resolver(Condition)\nexport class ConditionResolver {\n  @Query(() => [Condition], {\n    description: 'Gets all conditions, optionally filtered by name and sorted by name.'\n  })\n  async conditions(@Args(() => ConditionArgs) args: ConditionArgs): Promise<Condition[]> {\n    const validatedArgs = ConditionArgsSchema.parse(args)\n    const query = ConditionModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<ConditionOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: CONDITION_SORT_FIELD_MAP,\n      defaultSortField: ConditionOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Condition, { nullable: true, description: 'Gets a single condition by index.' })\n  async condition(@Arg('index', () => String) indexInput: string): Promise<Condition | null> {\n    const { index } = ConditionIndexArgsSchema.parse({ index: indexInput })\n    return ConditionModel.findOne({ index }).lean()\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/damageType/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum DamageTypeOrderField {\n  NAME = 'name'\n}\n\nexport const DAMAGE_TYPE_SORT_FIELD_MAP: Record<DamageTypeOrderField, string> = {\n  [DamageTypeOrderField.NAME]: 'name'\n}\n\nregisterEnumType(DamageTypeOrderField, {\n  name: 'DamageTypeOrderField',\n  description: 'Fields to sort Damage Types by'\n})\n\n@InputType()\nexport class DamageTypeOrder implements BaseOrderInterface<DamageTypeOrderField> {\n  @Field(() => DamageTypeOrderField)\n  by!: DamageTypeOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => DamageTypeOrder, { nullable: true })\n  then_by?: DamageTypeOrder\n}\n\nexport const DamageTypeOrderSchema: z.ZodType<DamageTypeOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(DamageTypeOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: DamageTypeOrderSchema.optional()\n  })\n)\n\nexport const DamageTypeArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: DamageTypeOrderSchema.optional()\n})\n\nexport const DamageTypeIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class DamageTypeArgs extends BaseFilterArgs {\n  @Field(() => DamageTypeOrder, {\n    nullable: true,\n    description: 'Specify sorting order for damage types.'\n  })\n  order?: DamageTypeOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/damageType/resolver.ts",
    "content": "import { Arg, Args, Query, Resolver } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport DamageTypeModel, { DamageType } from '@/models/2014/damageType'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  DAMAGE_TYPE_SORT_FIELD_MAP,\n  DamageTypeArgs,\n  DamageTypeArgsSchema,\n  DamageTypeIndexArgsSchema,\n  DamageTypeOrderField\n} from './args'\n\n@Resolver(DamageType)\nexport class DamageTypeResolver {\n  @Query(() => [DamageType], {\n    description: 'Gets all damage types, optionally filtered by name and sorted by name.'\n  })\n  async damageTypes(@Args(() => DamageTypeArgs) args: DamageTypeArgs): Promise<DamageType[]> {\n    const validatedArgs = DamageTypeArgsSchema.parse(args)\n    const query = DamageTypeModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<DamageTypeOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: DAMAGE_TYPE_SORT_FIELD_MAP,\n      defaultSortField: DamageTypeOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => DamageType, { nullable: true, description: 'Gets a single damage type by index.' })\n  async damageType(@Arg('index', () => String) indexInput: string): Promise<DamageType | null> {\n    const { index } = DamageTypeIndexArgsSchema.parse({ index: indexInput })\n    return DamageTypeModel.findOne({ index }).lean()\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/equipment/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum EquipmentOrderField {\n  NAME = 'name',\n  WEIGHT = 'weight',\n  COST_QUANTITY = 'cost_quantity'\n}\n\nexport const EQUIPMENT_SORT_FIELD_MAP: Record<EquipmentOrderField, string> = {\n  [EquipmentOrderField.NAME]: 'name',\n  [EquipmentOrderField.WEIGHT]: 'weight',\n  [EquipmentOrderField.COST_QUANTITY]: 'cost.quantity'\n}\n\nregisterEnumType(EquipmentOrderField, {\n  name: 'EquipmentOrderField',\n  description: 'Fields to sort Equipment by'\n})\n\n@InputType()\nexport class EquipmentOrder implements BaseOrderInterface<EquipmentOrderField> {\n  @Field(() => EquipmentOrderField)\n  by!: EquipmentOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => EquipmentOrder, { nullable: true })\n  then_by?: EquipmentOrder\n}\n\nexport const EquipmentOrderSchema: z.ZodType<EquipmentOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(EquipmentOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: EquipmentOrderSchema.optional() // Simplified\n  })\n)\n\nexport const EquipmentArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  equipment_category: z.array(z.string()).optional(),\n  order: EquipmentOrderSchema.optional()\n})\n\nexport const EquipmentIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class EquipmentArgs extends BaseFilterArgs {\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by one or more equipment category indices (e.g., [\"weapon\", \"armor\"])'\n  })\n  equipment_category?: string[]\n\n  @Field(() => EquipmentOrder, {\n    nullable: true,\n    description: 'Specify sorting order for equipment.'\n  })\n  order?: EquipmentOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/equipment/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { AnyEquipment } from '@/graphql/2014/common/unions'\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { resolveMultipleReferences, resolveSingleReference } from '@/graphql/utils/resolvers'\nimport EquipmentModel, { Content, Equipment } from '@/models/2014/equipment'\nimport WeaponPropertyModel, { WeaponProperty } from '@/models/2014/weaponProperty'\nimport { APIReference } from '@/models/common/apiReference'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  EQUIPMENT_SORT_FIELD_MAP,\n  EquipmentArgs,\n  EquipmentArgsSchema,\n  EquipmentIndexArgsSchema,\n  EquipmentOrderField\n} from './args'\n\n@Resolver(Equipment)\nexport class EquipmentResolver {\n  @Query(() => [AnyEquipment], {\n    description: 'Gets all equipment, optionally filtered and sorted.'\n  })\n  async equipments(\n    @Args(() => EquipmentArgs) args: EquipmentArgs\n  ): Promise<Array<typeof AnyEquipment>> {\n    const validatedArgs = EquipmentArgsSchema.parse(args)\n    const query = EquipmentModel.find()\n    const filters: any[] = []\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      filters.push({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    if (validatedArgs.equipment_category && validatedArgs.equipment_category.length > 0) {\n      filters.push({ 'equipment_category.index': { $in: validatedArgs.equipment_category } })\n    }\n\n    if (filters.length > 0) {\n      query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<EquipmentOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: EQUIPMENT_SORT_FIELD_MAP,\n      defaultSortField: EquipmentOrderField.NAME\n    })\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => AnyEquipment, {\n    nullable: true,\n    description: 'Gets a single piece of equipment by its index.'\n  })\n  async equipment(\n    @Arg('index', () => String) indexInput: string\n  ): Promise<typeof AnyEquipment | null> {\n    const { index } = EquipmentIndexArgsSchema.parse({ index: indexInput })\n    return EquipmentModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => [WeaponProperty], { nullable: true })\n  async properties(@Root() equipment: Equipment): Promise<WeaponProperty[] | null> {\n    if (!equipment.properties) return null\n    return resolveMultipleReferences(equipment.properties, WeaponPropertyModel)\n  }\n}\n@Resolver(Content)\nexport class ContentFieldResolver {\n  @FieldResolver(() => AnyEquipment, {\n    nullable: true,\n    description: 'Resolves the APIReference to the actual Equipment.'\n  })\n  async item(@Root() content: Content): Promise<typeof AnyEquipment | null> {\n    const itemRef: APIReference = content.item\n\n    if (!itemRef?.index) return null\n\n    return resolveSingleReference(itemRef, EquipmentModel)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/equipmentCategory/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum EquipmentCategoryOrderField {\n  NAME = 'name'\n}\n\nexport const EQUIPMENT_CATEGORY_SORT_FIELD_MAP: Record<EquipmentCategoryOrderField, string> = {\n  [EquipmentCategoryOrderField.NAME]: 'name'\n}\n\nregisterEnumType(EquipmentCategoryOrderField, {\n  name: 'EquipmentCategoryOrderField',\n  description: 'Fields to sort Equipment Categories by'\n})\n\n@InputType()\nexport class EquipmentCategoryOrder implements BaseOrderInterface<EquipmentCategoryOrderField> {\n  @Field(() => EquipmentCategoryOrderField)\n  by!: EquipmentCategoryOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => EquipmentCategoryOrder, { nullable: true })\n  then_by?: EquipmentCategoryOrder\n}\n\nexport const EquipmentCategoryOrderSchema: z.ZodType<EquipmentCategoryOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(EquipmentCategoryOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: EquipmentCategoryOrderSchema.optional()\n  })\n)\n\nexport const EquipmentCategoryArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: EquipmentCategoryOrderSchema.optional()\n})\n\nexport const EquipmentCategoryIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class EquipmentCategoryArgs extends BaseFilterArgs {\n  @Field(() => EquipmentCategoryOrder, {\n    nullable: true,\n    description: 'Specify sorting order for equipment categories.'\n  })\n  order?: EquipmentCategoryOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/equipmentCategory/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { EquipmentOrMagicItem } from '@/graphql/2014/common/unions'\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport EquipmentModel, { Equipment } from '@/models/2014/equipment'\nimport EquipmentCategoryModel, { EquipmentCategory } from '@/models/2014/equipmentCategory'\nimport MagicItemModel, { MagicItem } from '@/models/2014/magicItem'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  EQUIPMENT_CATEGORY_SORT_FIELD_MAP,\n  EquipmentCategoryArgs,\n  EquipmentCategoryArgsSchema,\n  EquipmentCategoryIndexArgsSchema,\n  EquipmentCategoryOrderField\n} from './args'\n\n@Resolver(EquipmentCategory)\nexport class EquipmentCategoryResolver {\n  @Query(() => [EquipmentCategory], {\n    description: 'Gets all equipment categories, optionally filtered by name and sorted by name.'\n  })\n  async equipmentCategories(\n    @Args(() => EquipmentCategoryArgs) args: EquipmentCategoryArgs\n  ): Promise<EquipmentCategory[]> {\n    const validatedArgs = EquipmentCategoryArgsSchema.parse(args)\n    const query = EquipmentCategoryModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<EquipmentCategoryOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: EQUIPMENT_CATEGORY_SORT_FIELD_MAP,\n      defaultSortField: EquipmentCategoryOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => EquipmentCategory, {\n    nullable: true,\n    description: 'Gets a single equipment category by index.'\n  })\n  async equipmentCategory(\n    @Arg('index', () => String) indexInput: string\n  ): Promise<EquipmentCategory | null> {\n    const { index } = EquipmentCategoryIndexArgsSchema.parse({ index: indexInput })\n    return EquipmentCategoryModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => [EquipmentOrMagicItem])\n  async equipment(\n    @Root() equipmentCategory: EquipmentCategory\n  ): Promise<(Equipment | MagicItem)[]> {\n    if (equipmentCategory.equipment.length === 0) {\n      return []\n    }\n\n    const equipmentIndices = equipmentCategory.equipment.map((ref) => ref.index)\n\n    // Fetch both Equipment and MagicItems matching the indices\n    const [equipments, magicItems] = await Promise.all([\n      EquipmentModel.find({ index: { $in: equipmentIndices } }).lean(),\n      MagicItemModel.find({ index: { $in: equipmentIndices } }).lean()\n    ])\n\n    return [...equipments, ...magicItems]\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/feat/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum FeatOrderField {\n  NAME = 'name'\n}\n\nexport const FEAT_SORT_FIELD_MAP: Record<FeatOrderField, string> = {\n  [FeatOrderField.NAME]: 'name'\n}\n\nregisterEnumType(FeatOrderField, {\n  name: 'FeatOrderField',\n  description: 'Fields to sort Feats by'\n})\n\n@InputType()\nexport class FeatOrder implements BaseOrderInterface<FeatOrderField> {\n  @Field(() => FeatOrderField)\n  by!: FeatOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => FeatOrder, { nullable: true })\n  then_by?: FeatOrder\n}\n\nexport const FeatOrderSchema: z.ZodType<FeatOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(FeatOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: FeatOrderSchema.optional()\n  })\n)\n\nexport const FeatArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: FeatOrderSchema.optional()\n})\n\nexport const FeatIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class FeatArgs extends BaseFilterArgs {\n  @Field(() => FeatOrder, {\n    nullable: true,\n    description: 'Specify sorting order for feats.'\n  })\n  order?: FeatOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/feat/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { resolveSingleReference } from '@/graphql/utils/resolvers'\nimport AbilityScoreModel, { AbilityScore } from '@/models/2014/abilityScore'\nimport FeatModel, { Feat, Prerequisite } from '@/models/2014/feat'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  FEAT_SORT_FIELD_MAP,\n  FeatArgs,\n  FeatArgsSchema,\n  FeatIndexArgsSchema,\n  FeatOrderField\n} from './args'\n\n@Resolver(Feat)\nexport class FeatResolver {\n  @Query(() => [Feat], {\n    description: 'Gets all feats, optionally filtered by name and sorted by name.'\n  })\n  async feats(@Args(() => FeatArgs) args: FeatArgs): Promise<Feat[]> {\n    const validatedArgs = FeatArgsSchema.parse(args)\n    const query = FeatModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<FeatOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: FEAT_SORT_FIELD_MAP,\n      defaultSortField: FeatOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Feat, { nullable: true, description: 'Gets a single feat by index.' })\n  async feat(@Arg('index', () => String) indexInput: string): Promise<Feat | null> {\n    const { index } = FeatIndexArgsSchema.parse({ index: indexInput })\n    return FeatModel.findOne({ index }).lean()\n  }\n}\n\n@Resolver(Prerequisite)\nexport class PrerequisiteResolver {\n  @FieldResolver(() => AbilityScore, { nullable: true })\n  async ability_score(@Root() prerequisite: Prerequisite): Promise<AbilityScore | null> {\n    return resolveSingleReference(prerequisite.ability_score, AbilityScoreModel)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/feature/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\nimport { NumberFilterInput, NumberFilterInputSchema } from '@/graphql/common/inputs'\n\nexport enum FeatureOrderField {\n  NAME = 'name',\n  LEVEL = 'level',\n  CLASS = 'class',\n  SUBCLASS = 'subclass'\n}\n\nexport const FEATURE_SORT_FIELD_MAP: Record<FeatureOrderField, string> = {\n  [FeatureOrderField.NAME]: 'name',\n  [FeatureOrderField.LEVEL]: 'level',\n  [FeatureOrderField.CLASS]: 'class.name',\n  [FeatureOrderField.SUBCLASS]: 'subclass.name'\n}\n\nregisterEnumType(FeatureOrderField, {\n  name: 'FeatureOrderField',\n  description: 'Fields to sort Features by'\n})\n\n@InputType()\nexport class FeatureOrder implements BaseOrderInterface<FeatureOrderField> {\n  @Field(() => FeatureOrderField)\n  by!: FeatureOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => FeatureOrder, { nullable: true })\n  then_by?: FeatureOrder\n}\n\nexport const FeatureOrderSchema: z.ZodType<FeatureOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(FeatureOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: FeatureOrderSchema.optional()\n  })\n)\n\nexport const FeatureArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  level: NumberFilterInputSchema.optional(),\n  class: z.array(z.string()).optional(),\n  subclass: z.array(z.string()).optional(),\n  order: FeatureOrderSchema.optional()\n})\n\nexport const FeatureIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class FeatureArgs extends BaseFilterArgs {\n  @Field(() => NumberFilterInput, {\n    nullable: true,\n    description: 'Filter by level. Allows exact match, list, or range.'\n  })\n  level?: NumberFilterInput\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by one or more associated class indices'\n  })\n  class?: string[]\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by one or more associated subclass indices'\n  })\n  subclass?: string[]\n\n  @Field(() => FeatureOrder, {\n    nullable: true,\n    description: 'Specify sorting order for features.'\n  })\n  order?: FeatureOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/feature/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { FeaturePrerequisiteUnion } from '@/graphql/2014/types/featureTypes'\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { buildMongoQueryFromNumberFilter } from '@/graphql/common/inputs'\nimport { resolveMultipleReferences, resolveSingleReference } from '@/graphql/utils/resolvers'\nimport ClassModel, { Class } from '@/models/2014/class'\nimport FeatureModel, {\n  Feature,\n  FeaturePrerequisite,\n  FeatureSpecific,\n  LevelPrerequisite,\n  SpellPrerequisite\n} from '@/models/2014/feature'\nimport SpellModel from '@/models/2014/spell'\nimport SubclassModel, { Subclass } from '@/models/2014/subclass'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  FEATURE_SORT_FIELD_MAP,\n  FeatureArgs,\n  FeatureArgsSchema,\n  FeatureIndexArgsSchema,\n  FeatureOrderField\n} from './args'\n\n@Resolver(Feature)\nexport class FeatureResolver {\n  @Query(() => [Feature], {\n    description: 'Gets all features, optionally filtered and sorted.'\n  })\n  async features(@Args(() => FeatureArgs) args: FeatureArgs): Promise<Feature[]> {\n    const validatedArgs = FeatureArgsSchema.parse(args)\n\n    const query = FeatureModel.find()\n    const filters: any[] = []\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      filters.push({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n    if (validatedArgs.level) {\n      const levelQuery = buildMongoQueryFromNumberFilter(validatedArgs.level)\n      if (levelQuery) {\n        filters.push({ level: levelQuery })\n      }\n    }\n    if (validatedArgs.class && validatedArgs.class.length > 0) {\n      filters.push({ 'class.index': { $in: validatedArgs.class } })\n    }\n    if (validatedArgs.subclass && validatedArgs.subclass.length > 0) {\n      filters.push({ 'subclass.index': { $in: validatedArgs.subclass } })\n    }\n\n    if (filters.length > 0) {\n      query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<FeatureOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: FEATURE_SORT_FIELD_MAP,\n      defaultSortField: FeatureOrderField.NAME\n    })\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Feature, { nullable: true, description: 'Gets a single feature by its index.' })\n  async feature(@Arg('index', () => String) indexInput: string): Promise<Feature | null> {\n    const { index } = FeatureIndexArgsSchema.parse({ index: indexInput })\n    return FeatureModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => Class, { nullable: true })\n  async class(@Root() feature: Feature): Promise<Class | null> {\n    return resolveSingleReference(feature.class, ClassModel)\n  }\n\n  @FieldResolver(() => Feature, { nullable: true })\n  async parent(@Root() feature: Feature): Promise<Feature | null> {\n    return resolveSingleReference(feature.parent, FeatureModel)\n  }\n\n  @FieldResolver(() => Subclass, { nullable: true })\n  async subclass(@Root() feature: Feature): Promise<Subclass | null> {\n    return resolveSingleReference(feature.subclass, SubclassModel)\n  }\n\n  @FieldResolver(() => [FeaturePrerequisiteUnion], {\n    nullable: true,\n    description: 'Resolves the prerequisites array, fetching referenced Features or Spells.'\n  })\n  async prerequisites(\n    @Root() feature: Feature\n  ): Promise<Array<LevelPrerequisite | FeaturePrerequisite | SpellPrerequisite> | null> {\n    const prereqsData = feature.prerequisites\n\n    if (!prereqsData || prereqsData.length === 0) {\n      return null\n    }\n\n    const resolvedPrereqsPromises = prereqsData.map(\n      async (\n        prereq\n      ): Promise<LevelPrerequisite | FeaturePrerequisite | SpellPrerequisite | null> => {\n        switch (prereq.type) {\n          case 'level': {\n            return prereq as LevelPrerequisite\n          }\n          case 'feature': {\n            const featureUrl = (prereq as FeaturePrerequisite).feature\n            const referencedFeature = await FeatureModel.findOne({ url: featureUrl }).lean()\n            if (referencedFeature) {\n              return {\n                type: 'feature',\n                feature: referencedFeature\n              } as unknown as FeaturePrerequisite\n            } else {\n              console.warn(`Could not find prerequisite feature with url: ${featureUrl}`)\n              return null\n            }\n          }\n          case 'spell': {\n            const spellUrl = (prereq as SpellPrerequisite).spell\n            const referencedSpell = await SpellModel.findOne({ url: spellUrl }).lean()\n            if (referencedSpell) {\n              return {\n                type: 'spell',\n                spell: referencedSpell\n              } as unknown as SpellPrerequisite\n            } else {\n              console.warn(`Could not find prerequisite spell with index: ${spellUrl}`)\n              return null\n            }\n          }\n          default: {\n            console.warn(`Unknown prerequisite type found: ${prereq.type}`)\n            return null\n          }\n        }\n      }\n    )\n\n    const resolvedPrereqs = (await Promise.all(resolvedPrereqsPromises)).filter(\n      (p) => p !== null\n    ) as Array<LevelPrerequisite | FeaturePrerequisite | SpellPrerequisite>\n\n    return resolvedPrereqs.length > 0 ? resolvedPrereqs : null\n  }\n}\n\n@Resolver(FeatureSpecific)\nexport class FeatureSpecificResolver {\n  @FieldResolver(() => [Feature], { nullable: true })\n  async invocations(@Root() featureSpecific: FeatureSpecific): Promise<Feature[]> {\n    return resolveMultipleReferences(featureSpecific.invocations, FeatureModel)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/index.ts",
    "content": "// This file will export an array of all resolver classes\nimport { AbilityScoreResolver } from './abilityScore/resolver'\nimport { AlignmentResolver } from './alignment/resolver'\nimport { BackgroundResolver, EquipmentRefResolver } from './background/resolver'\nimport {\n  ClassEquipmentResolver,\n  ClassResolver,\n  MultiClassingPrereqResolver,\n  MultiClassingResolver\n} from './class/resolver'\nimport { ConditionResolver } from './condition/resolver'\nimport { DamageTypeResolver } from './damageType/resolver'\nimport { ContentFieldResolver, EquipmentResolver } from './equipment/resolver'\nimport { EquipmentCategoryResolver } from './equipmentCategory/resolver'\nimport { FeatResolver, PrerequisiteResolver } from './feat/resolver'\nimport { FeatureResolver, FeatureSpecificResolver } from './feature/resolver'\nimport { LanguageResolver } from './language/resolver'\nimport { LevelResolver } from './level/resolver'\nimport { MagicItemResolver } from './magicItem/resolver'\nimport { MagicSchoolResolver } from './magicSchool/resolver'\nimport {\n  ArmorClassArmorResolver,\n  ArmorClassConditionResolver,\n  ArmorClassSpellResolver,\n  DifficultyClassResolver,\n  MonsterActionResolver,\n  MonsterProficiencyResolver,\n  MonsterResolver,\n  SpecialAbilitySpellcastingResolver,\n  SpecialAbilitySpellResolver\n} from './monster/resolver'\nimport { ProficiencyResolver } from './proficiency/resolver'\nimport { RaceAbilityBonusResolver, RaceResolver } from './race/resolver'\nimport { RuleResolver } from './rule/resolver'\nimport { RuleSectionResolver } from './ruleSection/resolver'\nimport { SkillResolver } from './skill/resolver'\nimport { SpellDamageResolver, SpellResolver, SpellDCResolver } from './spell/resolver'\nimport { SubclassResolver, SubclassSpellResolver } from './subclass/resolver'\nimport { SubraceAbilityBonusResolver, SubraceResolver } from './subrace/resolver'\nimport { ActionDamageResolver, TraitResolver, TraitSpecificResolver } from './trait/resolver'\nimport { WeaponPropertyResolver } from './weaponProperty/resolver'\n\nconst collectionResolvers = [\n  AbilityScoreResolver,\n  AlignmentResolver,\n  BackgroundResolver,\n  ClassResolver,\n  ConditionResolver,\n  DamageTypeResolver,\n  EquipmentCategoryResolver,\n  EquipmentResolver,\n  FeatResolver,\n  FeatureResolver,\n  LanguageResolver,\n  LevelResolver,\n  MagicItemResolver,\n  MagicSchoolResolver,\n  MonsterResolver,\n  ProficiencyResolver,\n  RaceResolver,\n  RuleResolver,\n  RuleSectionResolver,\n  SkillResolver,\n  SpellResolver,\n  SubclassResolver,\n  SubraceResolver,\n  TraitResolver,\n  WeaponPropertyResolver\n] as const\n\nconst fieldResolvers = [\n  // Background\n  EquipmentRefResolver,\n  // Feat\n  PrerequisiteResolver,\n  // Trait\n  TraitSpecificResolver,\n  ActionDamageResolver,\n  // Feature\n  FeatureSpecificResolver,\n  // Race\n  RaceAbilityBonusResolver,\n  // Subrace\n  SubraceAbilityBonusResolver,\n  // Class\n  MultiClassingResolver,\n  MultiClassingPrereqResolver,\n  ClassEquipmentResolver,\n  // Subclass\n  SubclassSpellResolver,\n  // Spell\n  SpellDamageResolver,\n  SpellDCResolver,\n  // Equipment\n  ContentFieldResolver,\n  // Monster Field Resolvers\n  ArmorClassArmorResolver,\n  ArmorClassSpellResolver,\n  ArmorClassConditionResolver,\n  MonsterProficiencyResolver,\n  SpecialAbilitySpellcastingResolver,\n  SpecialAbilitySpellResolver,\n  MonsterActionResolver,\n  DifficultyClassResolver\n] as const\n\n// Export a new mutable array combining the readonly ones\nexport const resolvers = [...collectionResolvers, ...fieldResolvers] as const\n"
  },
  {
    "path": "src/graphql/2014/resolvers/language/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum LanguageOrderField {\n  NAME = 'name',\n  TYPE = 'type',\n  SCRIPT = 'script'\n}\n\nexport const LANGUAGE_SORT_FIELD_MAP: Record<LanguageOrderField, string> = {\n  [LanguageOrderField.NAME]: 'name',\n  [LanguageOrderField.TYPE]: 'type',\n  [LanguageOrderField.SCRIPT]: 'script'\n}\n\nregisterEnumType(LanguageOrderField, {\n  name: 'LanguageOrderField',\n  description: 'Fields to sort Languages by'\n})\n\n@InputType()\nexport class LanguageOrder implements BaseOrderInterface<LanguageOrderField> {\n  @Field(() => LanguageOrderField)\n  by!: LanguageOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => LanguageOrder, { nullable: true })\n  then_by?: LanguageOrder\n}\n\nexport const LanguageOrderSchema: z.ZodType<LanguageOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(LanguageOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: LanguageOrderSchema.optional()\n  })\n)\n\nexport const LanguageArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  type: z.string().optional(),\n  script: z.array(z.string()).optional(),\n  order: LanguageOrderSchema.optional()\n})\n\nexport const LanguageIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class LanguageArgs extends BaseFilterArgs {\n  @Field(() => String, {\n    nullable: true,\n    description:\n      'Filter by language type (e.g., Standard, Exotic) - case-insensitive exact match after normalization'\n  })\n  type?: string\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by one or more language scripts (e.g., [\"Common\", \"Elvish\"])'\n  })\n  script?: string[]\n\n  @Field(() => LanguageOrder, {\n    nullable: true,\n    description: 'Specify sorting order for languages.'\n  })\n  order?: LanguageOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/language/resolver.ts",
    "content": "import { Arg, Args, Query, Resolver } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport LanguageModel, { Language } from '@/models/2014/language'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  LANGUAGE_SORT_FIELD_MAP,\n  LanguageArgs,\n  LanguageArgsSchema,\n  LanguageIndexArgsSchema,\n  LanguageOrderField\n} from './args'\n\n@Resolver(Language)\nexport class LanguageResolver {\n  @Query(() => Language, { nullable: true, description: 'Gets a single language by its index.' })\n  async language(@Arg('index', () => String) indexInput: string): Promise<Language | null> {\n    const { index } = LanguageIndexArgsSchema.parse({ index: indexInput })\n    return LanguageModel.findOne({ index }).lean()\n  }\n\n  @Query(() => [Language], { description: 'Gets all languages, optionally filtered and sorted.' })\n  async languages(@Args(() => LanguageArgs) args: LanguageArgs): Promise<Language[]> {\n    const validatedArgs = LanguageArgsSchema.parse(args)\n\n    const query = LanguageModel.find()\n    const filters: any[] = []\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      filters.push({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    if (validatedArgs.type != null && validatedArgs.type !== '') {\n      filters.push({ type: { $regex: new RegExp(escapeRegExp(validatedArgs.type), 'i') } })\n    }\n\n    if (validatedArgs.script && validatedArgs.script.length > 0) {\n      filters.push({ script: { $in: validatedArgs.script } })\n    }\n\n    if (filters.length > 0) {\n      query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<LanguageOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: LANGUAGE_SORT_FIELD_MAP,\n      defaultSortField: LanguageOrderField.NAME\n    })\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip !== undefined) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit !== undefined) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/level/args.ts",
    "content": "import { ArgsType, Field, InputType, Int, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseIndexArgsSchema,\n  BaseOrderInterface,\n  BasePaginationArgs,\n  BasePaginationArgsSchema\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\nimport { NumberFilterInput, NumberFilterInputSchema } from '@/graphql/common/inputs'\n\nexport enum LevelOrderField {\n  LEVEL = 'level',\n  CLASS = 'class',\n  SUBCLASS = 'subclass'\n}\n\nexport const LEVEL_SORT_FIELD_MAP: Record<LevelOrderField, string> = {\n  [LevelOrderField.LEVEL]: 'level',\n  [LevelOrderField.CLASS]: 'class.name',\n  [LevelOrderField.SUBCLASS]: 'subclass.name'\n}\n\nregisterEnumType(LevelOrderField, {\n  name: 'LevelOrderField',\n  description: 'Fields to sort Levels by'\n})\n\n@InputType()\nexport class LevelOrder implements BaseOrderInterface<LevelOrderField> {\n  @Field(() => LevelOrderField)\n  by!: LevelOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => LevelOrder, { nullable: true })\n  then_by?: LevelOrder\n}\n\nexport const LevelOrderSchema: z.ZodType<LevelOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(LevelOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: LevelOrderSchema.optional()\n  })\n)\n\nexport const LevelArgsSchema = z.object({\n  ...BasePaginationArgsSchema.shape,\n  class: z.array(z.string()).optional(),\n  subclass: z.array(z.string()).optional(),\n  level: NumberFilterInputSchema.optional(),\n  ability_score_bonuses: z.number().int().optional(),\n  prof_bonus: z.number().int().optional(),\n  order: LevelOrderSchema.optional()\n})\n\nexport const LevelIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class LevelArgs extends BasePaginationArgs {\n  @Field(() => [String], { nullable: true, description: 'Filter by one or more class indices' })\n  class?: string[]\n\n  @Field(() => [String], { nullable: true, description: 'Filter by one or more subclass indices' })\n  subclass?: string[]\n\n  @Field(() => NumberFilterInput, {\n    nullable: true,\n    description: 'Filter by level. Allows exact match, list, or range.'\n  })\n  level?: NumberFilterInput\n\n  @Field(() => Int, {\n    nullable: true,\n    description: 'Filter by the exact number of ability score bonuses granted at this level.'\n  })\n  ability_score_bonuses?: number\n\n  @Field(() => Int, {\n    nullable: true,\n    description: 'Filter by the exact proficiency bonus at this level.'\n  })\n  prof_bonus?: number\n\n  @Field(() => LevelOrder, {\n    nullable: true,\n    description:\n      'Specify sorting order for levels. Allows nested sorting. Defaults to LEVEL ascending.'\n  })\n  order?: LevelOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/level/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { buildMongoQueryFromNumberFilter } from '@/graphql/common/inputs'\nimport { resolveMultipleReferences, resolveSingleReference } from '@/graphql/utils/resolvers'\nimport ClassModel, { Class } from '@/models/2014/class'\nimport FeatureModel, { Feature } from '@/models/2014/feature'\nimport LevelModel, { Level } from '@/models/2014/level'\nimport SubclassModel, { Subclass } from '@/models/2014/subclass'\n\nimport {\n  LEVEL_SORT_FIELD_MAP,\n  LevelArgs,\n  LevelArgsSchema,\n  LevelIndexArgsSchema,\n  LevelOrderField\n} from './args'\n\n@Resolver(Level)\nexport class LevelResolver {\n  @Query(() => Level, {\n    nullable: true,\n    description:\n      'Gets a single level by its combined index (e.g., wizard-3-evocation or fighter-5).'\n  })\n  async level(@Arg('index', () => String) indexInput: string): Promise<Level | null> {\n    const { index } = LevelIndexArgsSchema.parse({ index: indexInput })\n    return LevelModel.findOne({ index }).lean()\n  }\n\n  @Query(() => [Level], { description: 'Gets all levels, optionally filtered and sorted.' })\n  async levels(@Args(() => LevelArgs) args: LevelArgs): Promise<Level[]> {\n    const validatedArgs = LevelArgsSchema.parse(args)\n\n    let query = LevelModel.find()\n    const filters: any[] = []\n\n    if (validatedArgs.class && validatedArgs.class.length > 0) {\n      filters.push({ 'class.index': { $in: validatedArgs.class } })\n    }\n\n    if (validatedArgs.subclass && validatedArgs.subclass.length > 0) {\n      filters.push({ 'subclass.index': { $in: validatedArgs.subclass } })\n    }\n\n    if (validatedArgs.level) {\n      const levelQuery = buildMongoQueryFromNumberFilter(validatedArgs.level)\n      if (levelQuery) {\n        filters.push({ level: levelQuery })\n      }\n    }\n\n    if (validatedArgs.ability_score_bonuses != null) {\n      filters.push({ ability_score_bonuses: validatedArgs.ability_score_bonuses })\n    }\n\n    if (validatedArgs.prof_bonus != null) {\n      filters.push({ prof_bonus: validatedArgs.prof_bonus })\n    }\n\n    if (filters.length > 0) {\n      query = query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<LevelOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: LEVEL_SORT_FIELD_MAP,\n      defaultSortField: LevelOrderField.LEVEL\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query = query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip !== undefined) {\n      query = query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit !== undefined) {\n      query = query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @FieldResolver(() => Class, { nullable: true })\n  async class(@Root() level: Level): Promise<Class | null> {\n    return resolveSingleReference(level.class, ClassModel)\n  }\n\n  @FieldResolver(() => Subclass, { nullable: true })\n  async subclass(@Root() level: Level): Promise<Subclass | null> {\n    return resolveSingleReference(level.subclass, SubclassModel)\n  }\n\n  @FieldResolver(() => [Feature])\n  async features(@Root() level: Level): Promise<Feature[]> {\n    return resolveMultipleReferences(level.features, FeatureModel)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/magicItem/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum MagicItemOrderField {\n  NAME = 'name',\n  EQUIPMENT_CATEGORY = 'equipment_category',\n  RARITY = 'rarity'\n}\n\nexport const MAGIC_ITEM_SORT_FIELD_MAP: Record<MagicItemOrderField, string> = {\n  [MagicItemOrderField.NAME]: 'name',\n  [MagicItemOrderField.EQUIPMENT_CATEGORY]: 'equipment_category.name',\n  [MagicItemOrderField.RARITY]: 'rarity.name'\n}\n\nregisterEnumType(MagicItemOrderField, {\n  name: 'MagicItemOrderField',\n  description: 'Fields to sort Magic Items by'\n})\n\n@InputType()\nexport class MagicItemOrder implements BaseOrderInterface<MagicItemOrderField> {\n  @Field(() => MagicItemOrderField)\n  by!: MagicItemOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => MagicItemOrder, { nullable: true })\n  then_by?: MagicItemOrder\n}\n\nexport const MagicItemOrderSchema: z.ZodType<MagicItemOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(MagicItemOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: MagicItemOrderSchema.optional()\n  })\n)\n\nexport const MagicItemArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  equipment_category: z.array(z.string()).optional(),\n  rarity: z.array(z.string()).optional(),\n  order: MagicItemOrderSchema.optional()\n})\n\nexport const MagicItemIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class MagicItemArgs extends BaseFilterArgs {\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by one or more equipment category indices (e.g., [\"armor\", \"weapon\"])'\n  })\n  equipment_category?: string[]\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by one or more rarity names (e.g., [\"Rare\", \"Legendary\"])'\n  })\n  rarity?: string[]\n\n  @Field(() => MagicItemOrder, {\n    nullable: true,\n    description: 'Specify sorting order for magic items.'\n  })\n  order?: MagicItemOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/magicItem/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { resolveMultipleReferences, resolveSingleReference } from '@/graphql/utils/resolvers'\nimport EquipmentCategoryModel, { EquipmentCategory } from '@/models/2014/equipmentCategory'\nimport MagicItemModel, { MagicItem } from '@/models/2014/magicItem'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  MAGIC_ITEM_SORT_FIELD_MAP,\n  MagicItemArgs,\n  MagicItemArgsSchema,\n  MagicItemIndexArgsSchema,\n  MagicItemOrderField\n} from './args'\n\n@Resolver(MagicItem)\nexport class MagicItemResolver {\n  @Query(() => [MagicItem], {\n    description: 'Gets all magic items, optionally filtered and sorted.'\n  })\n  async magicItems(@Args(() => MagicItemArgs) args: MagicItemArgs): Promise<MagicItem[]> {\n    const validatedArgs = MagicItemArgsSchema.parse(args)\n\n    let query = MagicItemModel.find()\n    const filters: any[] = []\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      filters.push({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    if (validatedArgs.equipment_category && validatedArgs.equipment_category.length > 0) {\n      filters.push({ 'equipment_category.index': { $in: validatedArgs.equipment_category } })\n    }\n\n    if (validatedArgs.rarity && validatedArgs.rarity.length > 0) {\n      filters.push({ 'rarity.name': { $in: validatedArgs.rarity } })\n    }\n\n    if (filters.length > 0) {\n      query = query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<MagicItemOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: MAGIC_ITEM_SORT_FIELD_MAP,\n      defaultSortField: MagicItemOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query = query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip !== undefined) {\n      query = query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit !== undefined) {\n      query = query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => MagicItem, { nullable: true, description: 'Gets a single magic item by index.' })\n  async magicItem(@Arg('index', () => String) indexInput: string): Promise<MagicItem | null> {\n    const { index } = MagicItemIndexArgsSchema.parse({ index: indexInput })\n    return MagicItemModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => EquipmentCategory, { nullable: true })\n  async equipment_category(@Root() magicItem: MagicItem): Promise<EquipmentCategory | null> {\n    return resolveSingleReference(magicItem.equipment_category, EquipmentCategoryModel)\n  }\n\n  @FieldResolver(() => [MagicItem], { nullable: true })\n  async variants(@Root() magicItem: MagicItem): Promise<MagicItem[]> {\n    return resolveMultipleReferences(magicItem.variants, MagicItemModel)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/magicSchool/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum MagicSchoolOrderField {\n  NAME = 'name'\n}\n\nexport const MAGIC_SCHOOL_SORT_FIELD_MAP: Record<MagicSchoolOrderField, string> = {\n  [MagicSchoolOrderField.NAME]: 'name'\n}\n\nregisterEnumType(MagicSchoolOrderField, {\n  name: 'MagicSchoolOrderField',\n  description: 'Fields to sort Magic Schools by'\n})\n\n@InputType()\nexport class MagicSchoolOrder implements BaseOrderInterface<MagicSchoolOrderField> {\n  @Field(() => MagicSchoolOrderField)\n  by!: MagicSchoolOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => MagicSchoolOrder, { nullable: true })\n  then_by?: MagicSchoolOrder\n}\n\nexport const MagicSchoolOrderSchema: z.ZodType<MagicSchoolOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(MagicSchoolOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: MagicSchoolOrderSchema.optional()\n  })\n)\n\nexport const MagicSchoolArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: MagicSchoolOrderSchema.optional()\n})\n\nexport const MagicSchoolIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class MagicSchoolArgs extends BaseFilterArgs {\n  @Field(() => MagicSchoolOrder, {\n    nullable: true,\n    description: 'Specify sorting order for magic schools.'\n  })\n  order?: MagicSchoolOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/magicSchool/resolver.ts",
    "content": "import { Arg, Args, Query, Resolver } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport MagicSchoolModel, { MagicSchool } from '@/models/2014/magicSchool'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  MAGIC_SCHOOL_SORT_FIELD_MAP,\n  MagicSchoolArgs,\n  MagicSchoolArgsSchema,\n  MagicSchoolIndexArgsSchema,\n  MagicSchoolOrderField\n} from './args'\n\n@Resolver(MagicSchool)\nexport class MagicSchoolResolver {\n  @Query(() => [MagicSchool], {\n    description: 'Gets all magic schools, optionally filtered by name and sorted by name.'\n  })\n  async magicSchools(@Args(() => MagicSchoolArgs) args: MagicSchoolArgs): Promise<MagicSchool[]> {\n    const validatedArgs = MagicSchoolArgsSchema.parse(args)\n    const query = MagicSchoolModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<MagicSchoolOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: MAGIC_SCHOOL_SORT_FIELD_MAP,\n      defaultSortField: MagicSchoolOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => MagicSchool, {\n    nullable: true,\n    description: 'Gets a single magic school by index.'\n  })\n  async magicSchool(@Arg('index', () => String) indexInput: string): Promise<MagicSchool | null> {\n    const { index } = MagicSchoolIndexArgsSchema.parse({ index: indexInput })\n    return MagicSchoolModel.findOne({ index }).lean()\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/monster/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\nimport { NumberFilterInput, NumberFilterInputSchema } from '@/graphql/common/inputs'\n\nexport enum MonsterOrderField {\n  NAME = 'name',\n  TYPE = 'type',\n  SIZE = 'size',\n  CHALLENGE_RATING = 'challenge_rating',\n  STRENGTH = 'strength',\n  DEXTERITY = 'dexterity',\n  CONSTITUTION = 'constitution',\n  INTELLIGENCE = 'intelligence',\n  WISDOM = 'wisdom',\n  CHARISMA = 'charisma'\n}\n\nexport const MONSTER_SORT_FIELD_MAP: Record<MonsterOrderField, string> = {\n  [MonsterOrderField.NAME]: 'name',\n  [MonsterOrderField.TYPE]: 'type',\n  [MonsterOrderField.SIZE]: 'size',\n  [MonsterOrderField.CHALLENGE_RATING]: 'challenge_rating',\n  [MonsterOrderField.STRENGTH]: 'strength',\n  [MonsterOrderField.DEXTERITY]: 'dexterity',\n  [MonsterOrderField.CONSTITUTION]: 'constitution',\n  [MonsterOrderField.INTELLIGENCE]: 'intelligence',\n  [MonsterOrderField.WISDOM]: 'wisdom',\n  [MonsterOrderField.CHARISMA]: 'charisma'\n}\n\nregisterEnumType(MonsterOrderField, {\n  name: 'MonsterOrderField',\n  description: 'Fields to sort Monsters by'\n})\n\n@InputType()\nexport class MonsterOrder implements BaseOrderInterface<MonsterOrderField> {\n  @Field(() => MonsterOrderField)\n  by!: MonsterOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => MonsterOrder, { nullable: true })\n  then_by?: MonsterOrder\n}\n\nexport const MonsterOrderSchema: z.ZodType<MonsterOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(MonsterOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: MonsterOrderSchema.optional()\n  })\n)\n\nexport const MonsterArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  type: z.string().optional(),\n  subtype: z.string().optional(),\n  challenge_rating: NumberFilterInputSchema.optional(),\n  size: z.string().optional(),\n  xp: NumberFilterInputSchema.optional(),\n  strength: NumberFilterInputSchema.optional(),\n  dexterity: NumberFilterInputSchema.optional(),\n  constitution: NumberFilterInputSchema.optional(),\n  intelligence: NumberFilterInputSchema.optional(),\n  wisdom: NumberFilterInputSchema.optional(),\n  charisma: NumberFilterInputSchema.optional(),\n  damage_vulnerabilities: z.array(z.string()).optional(),\n  damage_resistances: z.array(z.string()).optional(),\n  damage_immunities: z.array(z.string()).optional(),\n  condition_immunities: z.array(z.string()).optional(),\n  order: MonsterOrderSchema.optional()\n})\n\nexport const MonsterIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class MonsterArgs extends BaseFilterArgs {\n  @Field(() => String, {\n    nullable: true,\n    description: 'Filter by monster type (case-insensitive, exact match, e.g., \"beast\")'\n  })\n  type?: string\n\n  @Field(() => String, {\n    nullable: true,\n    description: 'Filter by monster subtype (case-insensitive, exact match, e.g., \"goblinoid\")'\n  })\n  subtype?: string\n\n  @Field(() => NumberFilterInput, {\n    nullable: true,\n    description: 'Filter by challenge rating'\n  })\n  challenge_rating?: NumberFilterInput\n\n  @Field(() => String, {\n    nullable: true,\n    description: 'Filter by monster size (exact match, e.g., \"Medium\")'\n  })\n  size?: string\n\n  @Field(() => NumberFilterInput, {\n    nullable: true,\n    description: 'Filter by monster XP'\n  })\n  xp?: NumberFilterInput\n\n  @Field(() => NumberFilterInput, {\n    nullable: true,\n    description: 'Filter by strength score'\n  })\n  strength?: NumberFilterInput\n\n  @Field(() => NumberFilterInput, {\n    nullable: true,\n    description: 'Filter by dexterity score'\n  })\n  dexterity?: NumberFilterInput\n\n  @Field(() => NumberFilterInput, {\n    nullable: true,\n    description: 'Filter by constitution score'\n  })\n  constitution?: NumberFilterInput\n\n  @Field(() => NumberFilterInput, {\n    nullable: true,\n    description: 'Filter by intelligence score'\n  })\n  intelligence?: NumberFilterInput\n\n  @Field(() => NumberFilterInput, {\n    nullable: true,\n    description: 'Filter by wisdom score'\n  })\n  wisdom?: NumberFilterInput\n\n  @Field(() => NumberFilterInput, {\n    nullable: true,\n    description: 'Filter by charisma score'\n  })\n  charisma?: NumberFilterInput\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by damage vulnerability indices'\n  })\n  damage_vulnerabilities?: string[]\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by damage resistance indices'\n  })\n  damage_resistances?: string[]\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by damage immunity indices'\n  })\n  damage_immunities?: string[]\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by condition immunity indices'\n  })\n  condition_immunities?: string[]\n\n  @Field(() => MonsterOrder, {\n    nullable: true,\n    description: 'Specify sorting order for monsters.'\n  })\n  order?: MonsterOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/monster/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { Armor } from '@/graphql/2014/common/equipmentTypes'\nimport {\n  DamageOrDamageChoiceUnion,\n  ActionChoice,\n  ActionChoiceOption,\n  BreathChoice,\n  BreathChoiceOption,\n  DamageChoice,\n  DamageChoiceOption,\n  MultipleActionChoiceOption,\n  MonsterArmorClassUnion\n} from '@/graphql/2014/types/monsterTypes'\nimport { normalizeCount } from '@/graphql/2014/utils/helpers'\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { buildMongoQueryFromNumberFilter } from '@/graphql/common/inputs'\nimport { SpellSlotCount } from '@/graphql/common/types'\nimport { resolveMultipleReferences, resolveSingleReference } from '@/graphql/utils/resolvers'\nimport AbilityScoreModel, { AbilityScore } from '@/models/2014/abilityScore'\nimport ConditionModel, { Condition } from '@/models/2014/condition'\nimport DamageTypeModel, { DamageType } from '@/models/2014/damageType'\nimport EquipmentModel from '@/models/2014/equipment'\nimport MonsterModel, {\n  ArmorClassArmor,\n  ArmorClassCondition,\n  ArmorClassSpell,\n  Monster,\n  MonsterAction,\n  MonsterProficiency,\n  SpecialAbilitySpell,\n  SpecialAbilitySpellcasting\n} from '@/models/2014/monster'\nimport ProficiencyModel, { Proficiency } from '@/models/2014/proficiency'\nimport SpellModel, { Spell } from '@/models/2014/spell'\nimport { APIReference } from '@/models/common/apiReference'\nimport {\n  ActionOption,\n  BreathOption,\n  Choice,\n  DamageOption,\n  OptionsArrayOptionSet\n} from '@/models/common/choice'\nimport { Damage } from '@/models/common/damage'\nimport { DifficultyClass } from '@/models/common/difficultyClass'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  MONSTER_SORT_FIELD_MAP,\n  MonsterArgs,\n  MonsterArgsSchema,\n  MonsterIndexArgsSchema,\n  MonsterOrderField\n} from './args'\n\n@Resolver(Monster)\nexport class MonsterResolver {\n  @Query(() => [Monster], {\n    description: 'Gets all monsters, optionally filtered and sorted.'\n  })\n  async monsters(@Args(() => MonsterArgs) args: MonsterArgs): Promise<Monster[]> {\n    const validatedArgs = MonsterArgsSchema.parse(args)\n    let query = MonsterModel.find()\n    const filters: any[] = []\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      filters.push({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n    if (validatedArgs.type != null && validatedArgs.type !== '') {\n      filters.push({ type: { $regex: new RegExp(`^${escapeRegExp(validatedArgs.type)}$`, 'i') } })\n    }\n    if (validatedArgs.subtype != null && validatedArgs.subtype !== '') {\n      filters.push({\n        subtype: { $regex: new RegExp(`^${escapeRegExp(validatedArgs.subtype)}$`, 'i') }\n      })\n    }\n    if (validatedArgs.challenge_rating) {\n      const crQuery = buildMongoQueryFromNumberFilter(validatedArgs.challenge_rating)\n      if (crQuery) filters.push({ challenge_rating: crQuery })\n    }\n    if (validatedArgs.size != null && validatedArgs.size !== '') {\n      filters.push({ size: validatedArgs.size })\n    }\n\n    if (validatedArgs.xp) {\n      const xpQuery = buildMongoQueryFromNumberFilter(validatedArgs.xp)\n      if (xpQuery) filters.push({ xp: xpQuery })\n    }\n\n    const abilityScores = [\n      'strength',\n      'dexterity',\n      'constitution',\n      'intelligence',\n      'wisdom',\n      'charisma'\n    ] as const\n    for (const score of abilityScores) {\n      if (validatedArgs[score]) {\n        const scoreQuery = buildMongoQueryFromNumberFilter(validatedArgs[score]!)\n        if (scoreQuery) filters.push({ [score]: scoreQuery })\n      }\n    }\n\n    if (validatedArgs.damage_vulnerabilities && validatedArgs.damage_vulnerabilities.length > 0) {\n      filters.push({\n        'damage_vulnerabilities.index': { $in: validatedArgs.damage_vulnerabilities }\n      })\n    }\n    if (validatedArgs.damage_resistances && validatedArgs.damage_resistances.length > 0) {\n      filters.push({ 'damage_resistances.index': { $in: validatedArgs.damage_resistances } })\n    }\n    if (validatedArgs.damage_immunities && validatedArgs.damage_immunities.length > 0) {\n      filters.push({ 'damage_immunities.index': { $in: validatedArgs.damage_immunities } })\n    }\n    if (validatedArgs.condition_immunities && validatedArgs.condition_immunities.length > 0) {\n      filters.push({ 'condition_immunities.index': { $in: validatedArgs.condition_immunities } })\n    }\n\n    if (filters.length > 0) {\n      query = query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<MonsterOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: MONSTER_SORT_FIELD_MAP,\n      defaultSortField: MonsterOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query = query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip !== undefined) {\n      query = query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit !== undefined) {\n      query = query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Monster, { nullable: true, description: 'Gets a single monster by its index.' })\n  async monster(@Arg('index', () => String) indexInput: string): Promise<Monster | null> {\n    const { index } = MonsterIndexArgsSchema.parse({ index: indexInput })\n    return MonsterModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => [Condition])\n  async condition_immunities(@Root() monster: Monster): Promise<APIReference[]> {\n    return resolveMultipleReferences(monster.condition_immunities, ConditionModel)\n  }\n\n  @FieldResolver(() => [Monster])\n  async forms(@Root() monster: Monster): Promise<APIReference[] | null> {\n    if (!monster.forms) return null\n    return resolveMultipleReferences(monster.forms, MonsterModel)\n  }\n\n  @FieldResolver(() => [MonsterArmorClassUnion], { name: 'armor_class' })\n  async armor_class(@Root() monster: Monster): Promise<Array<typeof MonsterArmorClassUnion>> {\n    return monster.armor_class as Array<typeof MonsterArmorClassUnion>\n  }\n}\n\n@Resolver(ArmorClassArmor)\nexport class ArmorClassArmorResolver {\n  @FieldResolver(() => [Armor], { name: 'armor', nullable: true })\n  async armor(@Root() acArmor: ArmorClassArmor): Promise<Array<typeof Armor | null>> {\n    if (!acArmor.armor) return []\n    return resolveMultipleReferences(acArmor.armor, EquipmentModel) as Promise<\n      Array<typeof Armor | null>\n    >\n  }\n}\n\n@Resolver(ArmorClassSpell)\nexport class ArmorClassSpellResolver {\n  @FieldResolver(() => Spell, { name: 'spell', nullable: true })\n  async spell(@Root() acSpell: ArmorClassSpell): Promise<Spell | null> {\n    return resolveSingleReference(acSpell.spell, SpellModel)\n  }\n}\n\n@Resolver(ArmorClassCondition)\nexport class ArmorClassConditionResolver {\n  @FieldResolver(() => Condition, { name: 'condition', nullable: true })\n  async condition(@Root() acCondition: ArmorClassCondition): Promise<Condition | null> {\n    return resolveSingleReference(acCondition.condition, ConditionModel)\n  }\n}\n\n@Resolver(MonsterProficiency)\nexport class MonsterProficiencyResolver {\n  @FieldResolver(() => Proficiency, { name: 'proficiency' })\n  async proficiency(@Root() monsterProficiency: MonsterProficiency): Promise<Proficiency | null> {\n    return resolveSingleReference(monsterProficiency.proficiency, ProficiencyModel)\n  }\n}\n\n@Resolver(DifficultyClass)\nexport class DifficultyClassResolver {\n  @FieldResolver(() => AbilityScore, {\n    name: 'dc_type',\n    description: 'The ability score associated with this DC, resolved from its API reference.'\n  })\n  async dc_type(@Root() difficultyClass: DifficultyClass): Promise<AbilityScore | null> {\n    return resolveSingleReference(difficultyClass.dc_type as APIReference, AbilityScoreModel)\n  }\n}\n\n@Resolver(SpecialAbilitySpellcasting)\nexport class SpecialAbilitySpellcastingResolver {\n  @FieldResolver(() => AbilityScore, { name: 'ability' })\n  async ability(@Root() spellcasting: SpecialAbilitySpellcasting): Promise<AbilityScore | null> {\n    return resolveSingleReference(spellcasting.ability, AbilityScoreModel)\n  }\n\n  @FieldResolver(() => [SpellSlotCount], { name: 'slots', nullable: true })\n  async slots(@Root() spellcasting: SpecialAbilitySpellcasting): Promise<SpellSlotCount[] | null> {\n    if (!spellcasting.slots) {\n      return null\n    }\n    const slotCounts: SpellSlotCount[] = []\n    for (const levelKey in spellcasting.slots) {\n      if (Object.prototype.hasOwnProperty.call(spellcasting.slots, levelKey)) {\n        const count = spellcasting.slots[levelKey]\n        const slotLevel = parseInt(levelKey, 10)\n        if (!isNaN(slotLevel)) {\n          const slotCount = new SpellSlotCount()\n          slotCount.slot_level = slotLevel\n          slotCount.count = count\n          slotCounts.push(slotCount)\n        }\n      }\n    }\n    return slotCounts.sort((a, b) => a.slot_level - b.slot_level)\n  }\n}\n\n@Resolver(SpecialAbilitySpell)\nexport class SpecialAbilitySpellResolver {\n  @FieldResolver(() => Spell, { name: 'spell', description: 'The resolved spell object.' })\n  async resolveSpell(@Root() abilitySpell: SpecialAbilitySpell): Promise<Spell | null> {\n    const spellIndex = abilitySpell.url.substring(abilitySpell.url.lastIndexOf('/') + 1)\n    if (!spellIndex) return null\n    return SpellModel.findOne({ index: spellIndex })\n  }\n}\n\n@Resolver(MonsterAction)\nexport class MonsterActionResolver {\n  @FieldResolver(() => [DamageOrDamageChoiceUnion], { nullable: true })\n  async damage(@Root() action: MonsterAction): Promise<(Damage | DamageChoice)[] | undefined> {\n    if (!action.damage) {\n      return undefined\n    }\n\n    const resolvedDamage = await Promise.all(\n      action.damage.map(async (item: Damage | Choice) => {\n        if ('choose' in item) {\n          return resolveDamageChoice(item as Choice)\n        }\n        return item as Damage\n      })\n    )\n\n    return resolvedDamage.filter((item): item is Damage | DamageChoice => item !== null)\n  }\n\n  @FieldResolver(() => ActionChoice, { nullable: true })\n  async action_options(@Root() action: MonsterAction): Promise<ActionChoice | null> {\n    return resolveActionChoice(action.action_options)\n  }\n\n  @FieldResolver(() => BreathChoice, { nullable: true })\n  async options(@Root() action: MonsterAction): Promise<BreathChoice | null> {\n    return resolveBreathChoice(action.options)\n  }\n}\n\nasync function resolveBreathChoice(\n  choiceData: Choice | undefined | null\n): Promise<BreathChoice | null> {\n  if (!choiceData || !('options' in choiceData.from)) {\n    return null\n  }\n  const options = (choiceData.from as OptionsArrayOptionSet).options\n  const validOptions: BreathChoiceOption[] = []\n\n  for (const option of options) {\n    if (option.option_type === 'breath') {\n      const breathOption = option as BreathOption\n      const abilityScore = await resolveSingleReference(breathOption.dc.dc_type, AbilityScoreModel)\n\n      const currentResolvedOption: Partial<BreathChoiceOption> = {\n        option_type: breathOption.option_type,\n        name: breathOption.name,\n        dc: {\n          dc_type: abilityScore as AbilityScore,\n          dc_value: breathOption.dc.dc_value,\n          success_type: breathOption.dc.success_type\n        }\n      }\n\n      if (breathOption.damage && breathOption.damage.length > 0) {\n        const resolvedDamageItems = await Promise.all(\n          breathOption.damage.map(async (damageItem) => {\n            const resolvedDamageType = await resolveSingleReference(\n              damageItem.damage_type,\n              DamageTypeModel\n            )\n            if (resolvedDamageType !== null) {\n              return {\n                damage_dice: damageItem.damage_dice,\n                damage_type: resolvedDamageType as DamageType\n              }\n            }\n            return null\n          })\n        )\n\n        const filteredDamageItems = resolvedDamageItems.filter((item) => item !== null)\n        if (filteredDamageItems.length > 0) {\n          currentResolvedOption.damage = filteredDamageItems as any[]\n        }\n      }\n      validOptions.push(currentResolvedOption as BreathChoiceOption)\n    }\n  }\n\n  if (validOptions.length === 0) {\n    return null\n  }\n\n  return {\n    choose: choiceData.choose,\n    type: choiceData.type,\n    from: {\n      option_set_type: choiceData.from.option_set_type,\n      options: validOptions\n    },\n    desc: choiceData.desc\n  }\n}\n\nasync function resolveDamageChoice(\n  choiceData: Choice | undefined | null\n): Promise<DamageChoice | null> {\n  if (!choiceData || !('options' in choiceData.from)) {\n    return null\n  }\n  const options = (choiceData.from as OptionsArrayOptionSet).options\n  const validOptions: DamageChoiceOption[] = []\n\n  for (const option of options) {\n    if (option.option_type === 'damage') {\n      const damageOption = option as DamageOption\n      const damageType = await resolveSingleReference(damageOption.damage_type, DamageTypeModel)\n      if (damageType !== null) {\n        const resolvedOption: DamageChoiceOption = {\n          option_type: damageOption.option_type,\n          damage: {\n            damage_dice: damageOption.damage_dice,\n            damage_type: damageType as DamageType\n          }\n        }\n        validOptions.push(resolvedOption)\n      }\n    }\n  }\n\n  if (validOptions.length === 0) {\n    return null\n  }\n\n  return {\n    choose: choiceData.choose,\n    type: choiceData.type,\n    from: {\n      option_set_type: choiceData.from.option_set_type,\n      options: validOptions\n    },\n    desc: choiceData.desc\n  }\n}\n\nasync function resolveActionChoice(\n  choiceData: Choice | undefined | null\n): Promise<ActionChoice | null> {\n  if (!choiceData || !('options' in choiceData.from)) {\n    return null\n  }\n  const options = (choiceData.from as OptionsArrayOptionSet).options\n  const validOptions: Array<ActionChoiceOption | MultipleActionChoiceOption> = []\n\n  for (const option of options) {\n    if (option.option_type === 'multiple') {\n      const multipleOption = option as { option_type: string; items: ActionOption[] }\n      const resolvedItems = multipleOption.items.map((item) => ({\n        option_type: item.option_type,\n        action_name: item.action_name,\n        count: normalizeCount(item.count),\n        type: item.type\n      }))\n      validOptions.push({\n        option_type: multipleOption.option_type,\n        items: resolvedItems\n      })\n    } else if (option.option_type === 'action') {\n      const actionOption = option as ActionOption\n      const resolvedOption: ActionChoiceOption = {\n        option_type: actionOption.option_type,\n        action_name: actionOption.action_name,\n        count: normalizeCount(actionOption.count),\n        type: actionOption.type\n      }\n      validOptions.push(resolvedOption)\n    }\n  }\n\n  if (validOptions.length === 0) {\n    return null\n  }\n\n  return {\n    choose: choiceData.choose,\n    type: choiceData.type,\n    from: {\n      option_set_type: choiceData.from.option_set_type,\n      options: validOptions\n    },\n    desc: choiceData.desc\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/proficiency/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum ProficiencyOrderField {\n  NAME = 'name',\n  TYPE = 'type'\n}\n\nexport const PROFICIENCY_SORT_FIELD_MAP: Record<ProficiencyOrderField, string> = {\n  [ProficiencyOrderField.NAME]: 'name',\n  [ProficiencyOrderField.TYPE]: 'type'\n}\n\nregisterEnumType(ProficiencyOrderField, {\n  name: 'ProficiencyOrderField',\n  description: 'Fields to sort Proficiencies by'\n})\n\n@InputType()\nexport class ProficiencyOrder implements BaseOrderInterface<ProficiencyOrderField> {\n  @Field(() => ProficiencyOrderField)\n  by!: ProficiencyOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => ProficiencyOrder, { nullable: true })\n  then_by?: ProficiencyOrder\n}\n\nexport const ProficiencyOrderSchema: z.ZodType<ProficiencyOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(ProficiencyOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: ProficiencyOrderSchema.optional()\n  })\n)\n\nexport const ProficiencyArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  class: z.array(z.string()).optional(),\n  race: z.array(z.string()).optional(),\n  type: z.array(z.string()).optional(),\n  order: ProficiencyOrderSchema.optional()\n})\n\nexport const ProficiencyIndexArgsSchema = BaseIndexArgsSchema\n\n// Define ArgsType for the proficiencies query\n@ArgsType()\nexport class ProficiencyArgs extends BaseFilterArgs {\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by class index (e.g., [\"barbarian\", \"bard\"])'\n  })\n  class?: string[]\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by race index (e.g., [\"dragonborn\", \"dwarf\"])'\n  })\n  race?: string[]\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by proficiency type (exact match, e.g., [\"ARMOR\", \"WEAPONS\"])'\n  })\n  type?: string[]\n\n  @Field(() => ProficiencyOrder, {\n    nullable: true,\n    description: 'Specify sorting order for proficiencies. Allows nested sorting.'\n  })\n  order?: ProficiencyOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/proficiency/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { ProficiencyReference } from '@/graphql/2014/types/proficiencyTypes'\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { resolveMultipleReferences, resolveSingleReference } from '@/graphql/utils/resolvers'\nimport AbilityScoreModel from '@/models/2014/abilityScore'\nimport ClassModel, { Class } from '@/models/2014/class'\nimport EquipmentModel from '@/models/2014/equipment'\nimport EquipmentCategoryModel from '@/models/2014/equipmentCategory'\nimport ProficiencyModel, { Proficiency } from '@/models/2014/proficiency'\nimport RaceModel, { Race } from '@/models/2014/race'\nimport SkillModel from '@/models/2014/skill'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  PROFICIENCY_SORT_FIELD_MAP,\n  ProficiencyArgs,\n  ProficiencyArgsSchema,\n  ProficiencyIndexArgsSchema,\n  ProficiencyOrderField\n} from './args'\n\n@Resolver(Proficiency)\nexport class ProficiencyResolver {\n  @Query(() => [Proficiency], {\n    description: 'Query all Proficiencies, optionally filtered and sorted.'\n  })\n  async proficiencies(@Args(() => ProficiencyArgs) args: ProficiencyArgs): Promise<Proficiency[]> {\n    const validatedArgs = ProficiencyArgsSchema.parse(args)\n\n    let query = ProficiencyModel.find()\n    const filters: any[] = []\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      filters.push({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    if (validatedArgs.class && validatedArgs.class.length > 0) {\n      filters.push({ 'classes.index': { $in: validatedArgs.class } })\n    }\n\n    if (validatedArgs.race && validatedArgs.race.length > 0) {\n      filters.push({ 'races.index': { $in: validatedArgs.race } })\n    }\n\n    if (validatedArgs.type && validatedArgs.type.length > 0) {\n      filters.push({ type: { $in: validatedArgs.type } })\n    }\n\n    if (filters.length > 0) {\n      query = query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<ProficiencyOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: PROFICIENCY_SORT_FIELD_MAP,\n      defaultSortField: ProficiencyOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip !== undefined) {\n      query = query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit !== undefined) {\n      query = query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Proficiency, {\n    nullable: true,\n    description: 'Gets a single proficiency by index.'\n  })\n  async proficiency(@Arg('index', () => String) indexInput: string): Promise<Proficiency | null> {\n    const { index } = ProficiencyIndexArgsSchema.parse({ index: indexInput })\n    return ProficiencyModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => [Class], { nullable: true })\n  async classes(@Root() proficiency: Proficiency): Promise<Class[]> {\n    return resolveMultipleReferences(proficiency.classes, ClassModel)\n  }\n\n  @FieldResolver(() => [Race], { nullable: true })\n  async races(@Root() proficiency: Proficiency): Promise<Race[]> {\n    return resolveMultipleReferences(proficiency.races, RaceModel)\n  }\n\n  @FieldResolver(() => ProficiencyReference, { nullable: true })\n  async reference(@Root() proficiency: Proficiency): Promise<typeof ProficiencyReference | null> {\n    const ref = proficiency.reference\n    if (!ref?.index || !ref.url) {\n      return null\n    }\n\n    if (ref.url.includes('/equipment-categories/')) {\n      return resolveSingleReference(ref, EquipmentCategoryModel)\n    }\n    if (ref.url.includes('/skills/')) {\n      return resolveSingleReference(ref, SkillModel)\n    }\n    if (ref.url.includes('/ability-scores/')) {\n      return resolveSingleReference(ref, AbilityScoreModel)\n    }\n    if (ref.url.includes('/equipment/')) {\n      return resolveSingleReference(ref, EquipmentModel)\n    }\n\n    console.warn(\n      `Unable to determine reference type from URL: ${ref.url} (Proficiency index: ${proficiency.index})`\n    )\n    return null\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/race/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\nimport { NumberFilterInput, NumberFilterInputSchema } from '@/graphql/common/inputs'\n\nexport enum RaceOrderField {\n  NAME = 'name'\n}\n\nexport const RACE_SORT_FIELD_MAP: Record<RaceOrderField, string> = {\n  [RaceOrderField.NAME]: 'name'\n}\n\nregisterEnumType(RaceOrderField, {\n  name: 'RaceOrderField',\n  description: 'Fields to sort Races by'\n})\n\n@InputType()\nexport class RaceOrder implements BaseOrderInterface<RaceOrderField> {\n  @Field(() => RaceOrderField)\n  by!: RaceOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => RaceOrder, { nullable: true })\n  then_by?: RaceOrder\n}\n\nexport const RaceOrderSchema: z.ZodType<RaceOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(RaceOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: RaceOrderSchema.optional()\n  })\n)\n\nexport const RaceArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  ability_bonus: z.array(z.string()).optional(),\n  size: z.array(z.string()).optional(),\n  language: z.array(z.string()).optional(),\n  speed: NumberFilterInputSchema.optional(),\n  order: RaceOrderSchema.optional()\n})\n\nexport const RaceIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class RaceArgs extends BaseFilterArgs {\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by one or more ability score indices that provide a bonus'\n  })\n  ability_bonus?: string[]\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by one or more race sizes (e.g., [\"Medium\", \"Small\"])'\n  })\n  size?: string[]\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by one or more language indices spoken by the race'\n  })\n  language?: string[]\n\n  @Field(() => NumberFilterInput, {\n    nullable: true,\n    description: 'Filter by race speed. Allows exact match, list, or range.'\n  })\n  speed?: NumberFilterInput\n\n  @Field(() => RaceOrder, {\n    nullable: true,\n    description: 'Specify sorting order for races. Allows nested sorting.'\n  })\n  order?: RaceOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/race/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport {\n  AbilityScoreBonusChoice,\n  AbilityScoreBonusChoiceOption,\n  LanguageChoice\n} from '@/graphql/2014/common/choiceTypes'\nimport { resolveLanguageChoice } from '@/graphql/2014/utils/resolvers'\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { buildMongoQueryFromNumberFilter } from '@/graphql/common/inputs'\nimport { resolveMultipleReferences, resolveSingleReference } from '@/graphql/utils/resolvers'\nimport AbilityScoreModel, { AbilityScore } from '@/models/2014/abilityScore'\nimport LanguageModel, { Language } from '@/models/2014/language'\nimport RaceModel, { Race, RaceAbilityBonus } from '@/models/2014/race'\nimport SubraceModel, { Subrace } from '@/models/2014/subrace'\nimport TraitModel, { Trait } from '@/models/2014/trait'\nimport { AbilityBonusOption, Choice, OptionsArrayOptionSet } from '@/models/common/choice'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  RACE_SORT_FIELD_MAP,\n  RaceArgs,\n  RaceArgsSchema,\n  RaceIndexArgsSchema,\n  RaceOrderField\n} from './args'\n\n@Resolver(() => Race)\nexport class RaceResolver {\n  @Query(() => [Race], { description: 'Gets all races, optionally filtered by name and sorted.' })\n  async races(@Args(() => RaceArgs) args: RaceArgs): Promise<Race[]> {\n    const validatedArgs = RaceArgsSchema.parse(args)\n\n    const query = RaceModel.find()\n    const filters: any[] = []\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      filters.push({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    if (validatedArgs.ability_bonus && validatedArgs.ability_bonus.length > 0) {\n      filters.push({ 'ability_bonuses.ability_score.index': { $in: validatedArgs.ability_bonus } })\n    }\n\n    if (validatedArgs.size && validatedArgs.size.length > 0) {\n      filters.push({ size: { $in: validatedArgs.size } })\n    }\n\n    if (validatedArgs.language && validatedArgs.language.length > 0) {\n      filters.push({ 'languages.index': { $in: validatedArgs.language } })\n    }\n\n    if (validatedArgs.speed) {\n      const speedQuery = buildMongoQueryFromNumberFilter(validatedArgs.speed)\n      if (speedQuery) {\n        filters.push({ speed: speedQuery })\n      }\n    }\n\n    if (filters.length > 0) {\n      query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<RaceOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: RACE_SORT_FIELD_MAP,\n      defaultSortField: RaceOrderField.NAME\n    })\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip !== undefined) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit !== undefined) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Race, { nullable: true, description: 'Gets a single race by its index.' })\n  async race(@Arg('index', () => String) indexInput: string): Promise<Race | null> {\n    const { index } = RaceIndexArgsSchema.parse({ index: indexInput })\n    return RaceModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => [Language], { nullable: true })\n  async languages(@Root() race: Race): Promise<Language[]> {\n    return resolveMultipleReferences(race.languages, LanguageModel)\n  }\n\n  @FieldResolver(() => [Subrace], { nullable: true })\n  async subraces(@Root() race: Race): Promise<Subrace[]> {\n    return resolveMultipleReferences(race.subraces, SubraceModel)\n  }\n\n  @FieldResolver(() => [Trait], { nullable: true })\n  async traits(@Root() race: Race): Promise<Trait[]> {\n    return resolveMultipleReferences(race.traits, TraitModel)\n  }\n\n  @FieldResolver(() => LanguageChoice, { nullable: true })\n  async language_options(@Root() race: Race): Promise<LanguageChoice | null> {\n    return resolveLanguageChoice(race.language_options as Choice)\n  }\n\n  @FieldResolver(() => AbilityScoreBonusChoice, { nullable: true })\n  async ability_bonus_options(@Root() race: Race): Promise<AbilityScoreBonusChoice | null> {\n    return resolveAbilityScoreBonusChoice(race.ability_bonus_options, AbilityScoreModel)\n  }\n}\n\n@Resolver(RaceAbilityBonus)\nexport class RaceAbilityBonusResolver {\n  @FieldResolver(() => AbilityScore, { nullable: true })\n  async ability_score(@Root() raceAbilityBonus: RaceAbilityBonus): Promise<AbilityScore | null> {\n    return resolveSingleReference(raceAbilityBonus.ability_score, AbilityScoreModel)\n  }\n}\n\nasync function resolveAbilityScoreBonusChoice(\n  choiceData: Choice | undefined,\n  TargetAbilityScoreModel: typeof AbilityScoreModel\n): Promise<AbilityScoreBonusChoice | null> {\n  if (!choiceData || !choiceData.type || typeof choiceData.choose !== 'number') {\n    return null\n  }\n\n  const resolvedOptions: AbilityScoreBonusChoiceOption[] = []\n  const from = choiceData.from as OptionsArrayOptionSet\n\n  for (const option of from.options) {\n    if (option.option_type === 'ability_bonus') {\n      const abilityScore = await resolveSingleReference(\n        (option as AbilityBonusOption).ability_score,\n        TargetAbilityScoreModel\n      )\n\n      if (abilityScore !== null) {\n        resolvedOptions.push({\n          option_type: option.option_type,\n          ability_score: abilityScore as AbilityScore,\n          bonus: (option as AbilityBonusOption).bonus\n        })\n      }\n    }\n  }\n\n  if (resolvedOptions.length === 0 && from.options.length > 0) {\n    return null\n  }\n\n  return {\n    choose: choiceData.choose,\n    type: choiceData.type,\n    from: {\n      option_set_type: from.option_set_type,\n      options: resolvedOptions\n    },\n    desc: choiceData.desc\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/rule/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum RuleOrderField {\n  NAME = 'name'\n}\n\nexport const RULE_SORT_FIELD_MAP: Record<RuleOrderField, string> = {\n  [RuleOrderField.NAME]: 'name'\n}\n\nregisterEnumType(RuleOrderField, {\n  name: 'RuleOrderField',\n  description: 'Fields to sort Rules by'\n})\n\n@InputType()\nexport class RuleOrder implements BaseOrderInterface<RuleOrderField> {\n  @Field(() => RuleOrderField)\n  by!: RuleOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => RuleOrder, { nullable: true })\n  then_by?: RuleOrder\n}\n\nexport const RuleOrderSchema: z.ZodType<RuleOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(RuleOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: RuleOrderSchema.optional()\n  })\n)\n\nexport const RuleArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: RuleOrderSchema.optional()\n})\n\nexport const RuleIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class RuleArgs extends BaseFilterArgs {\n  @Field(() => RuleOrder, {\n    nullable: true,\n    description: 'Specify sorting order for rules. Allows nested sorting.'\n  })\n  order?: RuleOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/rule/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { resolveMultipleReferences } from '@/graphql/utils/resolvers'\nimport RuleModel, { Rule } from '@/models/2014/rule'\nimport RuleSectionModel, { RuleSection } from '@/models/2014/ruleSection'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  RULE_SORT_FIELD_MAP,\n  RuleArgs,\n  RuleArgsSchema,\n  RuleIndexArgsSchema,\n  RuleOrderField\n} from './args'\n\n@Resolver(Rule)\nexport class RuleResolver {\n  @Query(() => [Rule], {\n    description: 'Gets all rules, optionally filtered by name and sorted by name.'\n  })\n  async rules(@Args(() => RuleArgs) args: RuleArgs): Promise<Rule[]> {\n    const validatedArgs = RuleArgsSchema.parse(args)\n    const query = RuleModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<RuleOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: RULE_SORT_FIELD_MAP\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Rule, { nullable: true, description: 'Gets a single rule by index.' })\n  async rule(@Arg('index', () => String) indexInput: string): Promise<Rule | null> {\n    const { index } = RuleIndexArgsSchema.parse({ index: indexInput })\n    return RuleModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => [RuleSection])\n  async subsections(@Root() rule: Rule): Promise<RuleSection[]> {\n    return resolveMultipleReferences(rule.subsections, RuleSectionModel)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/ruleSection/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum RuleSectionOrderField {\n  NAME = 'name'\n}\n\nexport const RULE_SECTION_SORT_FIELD_MAP: Record<RuleSectionOrderField, string> = {\n  [RuleSectionOrderField.NAME]: 'name'\n}\n\nregisterEnumType(RuleSectionOrderField, {\n  name: 'RuleSectionOrderField',\n  description: 'Fields to sort Rule Sections by'\n})\n\n@InputType()\nexport class RuleSectionOrder implements BaseOrderInterface<RuleSectionOrderField> {\n  @Field(() => RuleSectionOrderField)\n  by!: RuleSectionOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => RuleSectionOrder, { nullable: true })\n  then_by?: RuleSectionOrder\n}\n\nexport const RuleSectionOrderSchema: z.ZodType<RuleSectionOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(RuleSectionOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: RuleSectionOrderSchema.optional()\n  })\n)\n\nexport const RuleSectionArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: RuleSectionOrderSchema.optional()\n})\n\nexport const RuleSectionIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class RuleSectionArgs extends BaseFilterArgs {\n  @Field(() => RuleSectionOrder, {\n    nullable: true,\n    description: 'Specify sorting order for rule sections. Allows nested sorting.'\n  })\n  order?: RuleSectionOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/ruleSection/resolver.ts",
    "content": "import { Arg, Args, Query, Resolver } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport RuleSectionModel, { RuleSection } from '@/models/2014/ruleSection'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  RULE_SECTION_SORT_FIELD_MAP,\n  RuleSectionArgs,\n  RuleSectionArgsSchema,\n  RuleSectionIndexArgsSchema,\n  RuleSectionOrderField\n} from './args'\n\n@Resolver(RuleSection)\nexport class RuleSectionResolver {\n  @Query(() => [RuleSection], {\n    description: 'Gets all rule sections, optionally filtered by name and sorted by name.'\n  })\n  async ruleSections(@Args(() => RuleSectionArgs) args: RuleSectionArgs): Promise<RuleSection[]> {\n    const validatedArgs = RuleSectionArgsSchema.parse(args)\n    const query = RuleSectionModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<RuleSectionOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: RULE_SECTION_SORT_FIELD_MAP\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => RuleSection, {\n    nullable: true,\n    description: 'Gets a single rule section by index.'\n  })\n  async ruleSection(@Arg('index', () => String) indexInput: string): Promise<RuleSection | null> {\n    const { index } = RuleSectionIndexArgsSchema.parse({ index: indexInput })\n    return RuleSectionModel.findOne({ index }).lean()\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/skill/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum SkillOrderField {\n  NAME = 'name',\n  ABILITY_SCORE = 'ability_score'\n}\n\nexport const SKILL_SORT_FIELD_MAP: Record<SkillOrderField, string> = {\n  [SkillOrderField.NAME]: 'name',\n  [SkillOrderField.ABILITY_SCORE]: 'ability_score.name'\n}\n\nregisterEnumType(SkillOrderField, {\n  name: 'SkillOrderField',\n  description: 'Fields to sort Skills by'\n})\n\n@InputType()\nexport class SkillOrder implements BaseOrderInterface<SkillOrderField> {\n  @Field(() => SkillOrderField)\n  by!: SkillOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => SkillOrder, { nullable: true })\n  then_by?: SkillOrder\n}\n\nexport const SkillOrderSchema: z.ZodType<SkillOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(SkillOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: SkillOrderSchema.optional()\n  })\n)\n\nexport const SkillArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  ability_score: z.array(z.string()).optional(),\n  order: SkillOrderSchema.optional()\n})\n\nexport const SkillIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class SkillArgs extends BaseFilterArgs {\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by ability score index (e.g., [\"str\", \"dex\"])'\n  })\n  ability_score?: string[]\n\n  @Field(() => SkillOrder, {\n    nullable: true,\n    description: 'Specify sorting order for skills.'\n  })\n  order?: SkillOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/skill/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { resolveSingleReference } from '@/graphql/utils/resolvers'\nimport AbilityScoreModel, { AbilityScore } from '@/models/2014/abilityScore'\nimport SkillModel, { Skill } from '@/models/2014/skill'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  SKILL_SORT_FIELD_MAP,\n  SkillArgs,\n  SkillArgsSchema,\n  SkillIndexArgsSchema,\n  SkillOrderField\n} from './args'\n\n@Resolver(Skill)\nexport class SkillResolver {\n  @Query(() => [Skill], {\n    description: 'Gets all skills, optionally filtered by name and sorted by name.'\n  })\n  async skills(@Args(() => SkillArgs) args: SkillArgs): Promise<Skill[]> {\n    const validatedArgs = SkillArgsSchema.parse(args)\n\n    const query = SkillModel.find()\n    const filters: any[] = []\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      filters.push({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    if (validatedArgs.ability_score && validatedArgs.ability_score.length > 0) {\n      filters.push({ 'ability_score.index': { $in: validatedArgs.ability_score } })\n    }\n\n    if (filters.length > 0) {\n      query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<SkillOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: SKILL_SORT_FIELD_MAP,\n      defaultSortField: SkillOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip !== undefined) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit !== undefined) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Skill, { nullable: true, description: 'Gets a single skill by index.' })\n  async skill(@Arg('index', () => String) indexInput: string): Promise<Skill | null> {\n    const { index } = SkillIndexArgsSchema.parse({ index: indexInput })\n    return SkillModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => AbilityScore)\n  async ability_score(@Root() skill: Skill): Promise<AbilityScore | null> {\n    return resolveSingleReference(skill.ability_score, AbilityScoreModel)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/spell/args.ts",
    "content": "import { ArgsType, Field, InputType, Int, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\nimport { NumberFilterInput, NumberFilterInputSchema } from '@/graphql/common/inputs'\n\nconst AreaOfEffectFilterInputSchema = z.object({\n  type: z.array(z.string()).optional(),\n  size: NumberFilterInputSchema.optional()\n})\n\n@InputType({\n  description: 'Input for filtering by area of effect properties.'\n})\nexport class AreaOfEffectFilterInput {\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by area of effect type (e.g., [\"sphere\", \"cone\"])'\n  })\n  type?: string[]\n\n  @Field(() => NumberFilterInput, {\n    nullable: true,\n    description: 'Filter by area of effect size (in feet).'\n  })\n  size?: NumberFilterInput\n}\n\n// Enum for Spell sortable fields\nexport enum SpellOrderField {\n  NAME = 'name',\n  LEVEL = 'level',\n  SCHOOL = 'school',\n  AREA_OF_EFFECT_SIZE = 'area_of_effect_size' // Matches old API\n}\n\nexport const SPELL_SORT_FIELD_MAP: Record<SpellOrderField, string> = {\n  [SpellOrderField.NAME]: 'name',\n  [SpellOrderField.LEVEL]: 'level',\n  [SpellOrderField.SCHOOL]: 'school.name',\n  [SpellOrderField.AREA_OF_EFFECT_SIZE]: 'area_of_effect.size'\n}\n\nregisterEnumType(SpellOrderField, {\n  name: 'SpellOrderField',\n  description: 'Fields to sort Spells by'\n})\n\n@InputType()\nexport class SpellOrder implements BaseOrderInterface<SpellOrderField> {\n  @Field(() => SpellOrderField)\n  by!: SpellOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => SpellOrder, { nullable: true })\n  then_by?: SpellOrder\n}\n\nexport const SpellOrderSchema: z.ZodType<SpellOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(SpellOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: SpellOrderSchema.optional()\n  })\n)\n\nexport const SpellArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  level: z.array(z.number().int().min(0).max(9)).optional(),\n  school: z.array(z.string()).optional(),\n  class: z.array(z.string()).optional(),\n  subclass: z.array(z.string()).optional(),\n  concentration: z.boolean().optional(),\n  ritual: z.boolean().optional(),\n  attack_type: z.array(z.string()).optional(),\n  casting_time: z.array(z.string()).optional(),\n  area_of_effect: AreaOfEffectFilterInputSchema.optional(),\n  damage_type: z.array(z.string()).optional(),\n  dc_type: z.array(z.string()).optional(),\n  range: z.array(z.string()).optional(),\n  order: SpellOrderSchema.optional()\n})\n\nexport const SpellIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class SpellArgs extends BaseFilterArgs {\n  @Field(() => [Int], {\n    nullable: true,\n    description: 'Filter by spell level (e.g., [0, 9])'\n  })\n  level?: number[]\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by magic school index (e.g., [\"evocation\"])'\n  })\n  school?: string[]\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by class index that can cast the spell (e.g., [\"wizard\"])'\n  })\n  class?: string[]\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by subclass index that can cast the spell (e.g., [\"lore\"])'\n  })\n  subclass?: string[]\n\n  @Field(() => Boolean, { nullable: true, description: 'Filter by concentration requirement' })\n  concentration?: boolean\n\n  @Field(() => Boolean, { nullable: true, description: 'Filter by ritual requirement' })\n  ritual?: boolean\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by attack type (e.g., [\"ranged\", \"melee\"])'\n  })\n  attack_type?: string[]\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by casting time (e.g., [\"1 action\"])'\n  })\n  casting_time?: string[]\n\n  @Field(() => AreaOfEffectFilterInput, {\n    nullable: true,\n    description: 'Filter by area of effect properties'\n  })\n  area_of_effect?: AreaOfEffectFilterInput\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by damage type index (e.g., [\"fire\"])'\n  })\n  damage_type?: string[]\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by saving throw DC type index (e.g., [\"dex\"])'\n  })\n  dc_type?: string[]\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by spell range (e.g., [\"Self\", \"Touch\"])'\n  })\n  range?: string[]\n\n  @Field(() => SpellOrder, {\n    nullable: true,\n    description: 'Specify sorting order for spells.'\n  })\n  order?: SpellOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/spell/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { mapLevelObjectToArray } from '@/graphql/2014/utils/helpers'\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { buildMongoQueryFromNumberFilter } from '@/graphql/common/inputs'\nimport { LevelValue } from '@/graphql/common/types'\nimport { resolveMultipleReferences, resolveSingleReference } from '@/graphql/utils/resolvers'\nimport AbilityScoreModel, { AbilityScore } from '@/models/2014/abilityScore'\nimport ClassModel, { Class } from '@/models/2014/class'\nimport DamageTypeModel, { DamageType } from '@/models/2014/damageType'\nimport MagicSchoolModel, { MagicSchool } from '@/models/2014/magicSchool'\nimport SpellModel, { Spell, SpellDamage, SpellDC } from '@/models/2014/spell'\nimport SubclassModel, { Subclass } from '@/models/2014/subclass'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  SPELL_SORT_FIELD_MAP,\n  SpellArgs,\n  SpellArgsSchema,\n  SpellIndexArgsSchema,\n  SpellOrderField\n} from './args'\n\n@Resolver(Spell)\nexport class SpellResolver {\n  @Query(() => [Spell], { description: 'Gets all spells, optionally filtered and sorted.' })\n  async spells(@Args(() => SpellArgs) args: SpellArgs): Promise<Spell[]> {\n    const validatedArgs = SpellArgsSchema.parse(args)\n\n    const query = SpellModel.find()\n    const filters: any[] = []\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      filters.push({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n    if (validatedArgs.level && validatedArgs.level.length > 0) {\n      filters.push({ level: { $in: validatedArgs.level } })\n    }\n    if (validatedArgs.school && validatedArgs.school.length > 0) {\n      filters.push({ 'school.index': { $in: validatedArgs.school } })\n    }\n    if (validatedArgs.class && validatedArgs.class.length > 0) {\n      filters.push({ 'classes.index': { $in: validatedArgs.class } })\n    }\n    if (validatedArgs.subclass && validatedArgs.subclass.length > 0) {\n      filters.push({ 'subclasses.index': { $in: validatedArgs.subclass } })\n    }\n    if (typeof validatedArgs.concentration === 'boolean') {\n      filters.push({ concentration: validatedArgs.concentration })\n    }\n    if (typeof validatedArgs.ritual === 'boolean') {\n      filters.push({ ritual: validatedArgs.ritual })\n    }\n    if (validatedArgs.attack_type && validatedArgs.attack_type.length > 0) {\n      filters.push({ attack_type: { $in: validatedArgs.attack_type } })\n    }\n    if (validatedArgs.casting_time && validatedArgs.casting_time.length > 0) {\n      filters.push({ casting_time: { $in: validatedArgs.casting_time } })\n    }\n    if (validatedArgs.area_of_effect) {\n      if (validatedArgs.area_of_effect.type && validatedArgs.area_of_effect.type.length > 0) {\n        filters.push({ 'area_of_effect.type': { $in: validatedArgs.area_of_effect.type } })\n      }\n      if (validatedArgs.area_of_effect.size) {\n        const sizeFilter = buildMongoQueryFromNumberFilter(validatedArgs.area_of_effect.size)\n        if (sizeFilter) {\n          filters.push({ 'area_of_effect.size': sizeFilter })\n        }\n      }\n    }\n    if (validatedArgs.damage_type && validatedArgs.damage_type.length > 0) {\n      filters.push({ 'damage.damage_type.index': { $in: validatedArgs.damage_type } })\n    }\n    if (validatedArgs.dc_type && validatedArgs.dc_type.length > 0) {\n      filters.push({ 'dc.dc_type.index': { $in: validatedArgs.dc_type } })\n    }\n    if (validatedArgs.range && validatedArgs.range.length > 0) {\n      filters.push({ range: { $in: validatedArgs.range } })\n    }\n\n    if (filters.length > 0) {\n      query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<SpellOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: SPELL_SORT_FIELD_MAP,\n      defaultSortField: SpellOrderField.NAME\n    })\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip !== undefined) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit !== undefined) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Spell, { nullable: true, description: 'Gets a single spell by its index.' })\n  async spell(@Arg('index', () => String) indexInput: string): Promise<Spell | null> {\n    const { index } = SpellIndexArgsSchema.parse({ index: indexInput })\n    return SpellModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => [Class], { nullable: true })\n  async classes(@Root() spell: Spell): Promise<Class[]> {\n    return resolveMultipleReferences(spell.classes, ClassModel)\n  }\n\n  @FieldResolver(() => MagicSchool, { nullable: true })\n  async school(@Root() spell: Spell): Promise<MagicSchool | null> {\n    return resolveSingleReference(spell.school, MagicSchoolModel)\n  }\n\n  @FieldResolver(() => [Subclass], { nullable: true })\n  async subclasses(@Root() spell: Spell): Promise<Subclass[]> {\n    return resolveMultipleReferences(spell.subclasses, SubclassModel)\n  }\n\n  @FieldResolver(() => [LevelValue], {\n    nullable: true,\n    description: 'Healing amount based on spell slot level, transformed from raw data.'\n  })\n  async heal_at_slot_level(@Root() spell: Spell): Promise<LevelValue[] | null> {\n    return mapLevelObjectToArray(spell.heal_at_slot_level)\n  }\n}\n\n@Resolver(SpellDamage)\nexport class SpellDamageResolver {\n  @FieldResolver(() => DamageType, { nullable: true })\n  async damage_type(@Root() spellDamage: SpellDamage): Promise<DamageType | null> {\n    return resolveSingleReference(spellDamage.damage_type, DamageTypeModel)\n  }\n\n  @FieldResolver(() => [LevelValue])\n  async damage_at_slot_level(@Root() spellDamage: SpellDamage): Promise<LevelValue[] | null> {\n    return mapLevelObjectToArray(spellDamage.damage_at_slot_level)\n  }\n\n  @FieldResolver(() => [LevelValue], {\n    nullable: true,\n    description: 'Damage scaling based on character level, transformed from raw data.'\n  })\n  async damage_at_character_level(@Root() spellDamage: SpellDamage): Promise<LevelValue[] | null> {\n    return mapLevelObjectToArray(spellDamage.damage_at_character_level)\n  }\n}\n\n@Resolver(SpellDC)\nexport class SpellDCResolver {\n  @FieldResolver(() => AbilityScore, { nullable: true })\n  async dc_type(@Root() spellDC: SpellDC): Promise<AbilityScore | null> {\n    return resolveSingleReference(spellDC.dc_type, AbilityScoreModel)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/subclass/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum SubclassOrderField {\n  NAME = 'name'\n}\n\nexport const SUBCLASS_SORT_FIELD_MAP: Record<SubclassOrderField, string> = {\n  [SubclassOrderField.NAME]: 'name'\n}\n\nregisterEnumType(SubclassOrderField, {\n  name: 'SubclassOrderField',\n  description: 'Fields to sort Subclasses by'\n})\n\n@InputType()\nexport class SubclassOrder implements BaseOrderInterface<SubclassOrderField> {\n  @Field(() => SubclassOrderField)\n  by!: SubclassOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => SubclassOrder, { nullable: true })\n  then_by?: SubclassOrder\n}\n\nexport const SubclassOrderSchema: z.ZodType<SubclassOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(SubclassOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: SubclassOrderSchema.optional()\n  })\n)\n\nexport const SubclassArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: SubclassOrderSchema.optional()\n})\n\nexport const SubclassIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class SubclassArgs extends BaseFilterArgs {\n  @Field(() => SubclassOrder, {\n    nullable: true,\n    description: 'Specify sorting order for subclasses.'\n  })\n  order?: SubclassOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/subclass/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { SubclassSpellPrerequisiteUnion } from '@/graphql/2014/types/subclassTypes'\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { resolveSingleReference } from '@/graphql/utils/resolvers'\nimport ClassModel, { Class } from '@/models/2014/class'\nimport FeatureModel, { Feature } from '@/models/2014/feature'\nimport LevelModel, { Level } from '@/models/2014/level'\nimport SpellModel, { Spell } from '@/models/2014/spell'\nimport SubclassModel, { Subclass, SubclassSpell } from '@/models/2014/subclass'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  SUBCLASS_SORT_FIELD_MAP,\n  SubclassArgs,\n  SubclassArgsSchema,\n  SubclassIndexArgsSchema,\n  SubclassOrderField\n} from './args'\n\n@Resolver(Subclass)\nexport class SubclassResolver {\n  @Query(() => [Subclass], {\n    description: 'Gets all subclasses, optionally filtered by name and sorted.'\n  })\n  async subclasses(@Args(() => SubclassArgs) args: SubclassArgs): Promise<Subclass[]> {\n    const validatedArgs = SubclassArgsSchema.parse(args)\n\n    const query = SubclassModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<SubclassOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: SUBCLASS_SORT_FIELD_MAP,\n      defaultSortField: SubclassOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Subclass, { nullable: true, description: 'Gets a single subclass by its index.' })\n  async subclass(@Arg('index', () => String) indexInput: string): Promise<Subclass | null> {\n    const { index } = SubclassIndexArgsSchema.parse({ index: indexInput })\n    return SubclassModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => Class, { nullable: true })\n  async class(@Root() subclass: Subclass): Promise<Class | null> {\n    return resolveSingleReference(subclass.class, ClassModel)\n  }\n\n  @FieldResolver(() => [Level], { nullable: true })\n  async subclass_levels(@Root() subclass: Subclass): Promise<Level[]> {\n    if (!subclass.index) return []\n\n    return LevelModel.find({ 'subclass.index': subclass.index }).sort({ level: 1 }).lean()\n  }\n}\n\n@Resolver(SubclassSpell)\nexport class SubclassSpellResolver {\n  @FieldResolver(() => [SubclassSpellPrerequisiteUnion], {\n    description: 'Resolves the prerequisites to actual Level or Feature objects.',\n    nullable: true\n  })\n  async prerequisites(\n    @Root() subclassSpell: SubclassSpell\n  ): Promise<Array<Level | Feature> | null> {\n    const prereqsData = subclassSpell.prerequisites\n\n    if (prereqsData.length === 0) {\n      return null\n    }\n\n    const resolvedPrereqs: Array<Level | Feature> = []\n\n    for (const prereq of prereqsData) {\n      if (prereq.type === 'level') {\n        const level = await LevelModel.findOne({ index: prereq.index }).lean()\n        if (level !== null) {\n          resolvedPrereqs.push(level)\n        }\n      } else if (prereq.type === 'feature') {\n        const feature = await FeatureModel.findOne({ index: prereq.index }).lean()\n        if (feature !== null) {\n          resolvedPrereqs.push(feature)\n        }\n      }\n    }\n\n    return resolvedPrereqs.length > 0 ? resolvedPrereqs : null\n  }\n\n  @FieldResolver(() => Spell, {\n    description: 'The spell gained.',\n    nullable: false\n  })\n  async spell(\n    @Root() subclassSpell: SubclassSpell\n  ): Promise<Spell | null> {\n    return SpellModel.findOne({ 'index': subclassSpell.spell.index }).lean()\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/subrace/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum SubraceOrderField {\n  NAME = 'name'\n}\n\nexport const SUBRACE_SORT_FIELD_MAP: Record<SubraceOrderField, string> = {\n  [SubraceOrderField.NAME]: 'name'\n}\n\nregisterEnumType(SubraceOrderField, {\n  name: 'SubraceOrderField',\n  description: 'Fields to sort Subraces by'\n})\n\n@InputType()\nexport class SubraceOrder implements BaseOrderInterface<SubraceOrderField> {\n  @Field(() => SubraceOrderField)\n  by!: SubraceOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => SubraceOrder, { nullable: true })\n  then_by?: SubraceOrder\n}\n\nexport const SubraceOrderSchema: z.ZodType<SubraceOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(SubraceOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: SubraceOrderSchema.optional()\n  })\n)\n\nexport const SubraceArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: SubraceOrderSchema.optional()\n})\n\nexport const SubraceIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class SubraceArgs extends BaseFilterArgs {\n  @Field(() => SubraceOrder, {\n    nullable: true,\n    description: 'Specify sorting order for subraces.'\n  })\n  order?: SubraceOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/subrace/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { resolveMultipleReferences, resolveSingleReference } from '@/graphql/utils/resolvers'\nimport AbilityScoreModel, { AbilityScore } from '@/models/2014/abilityScore'\nimport RaceModel, { Race } from '@/models/2014/race'\nimport SubraceModel, { Subrace, SubraceAbilityBonus } from '@/models/2014/subrace'\nimport TraitModel, { Trait } from '@/models/2014/trait'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  SUBRACE_SORT_FIELD_MAP,\n  SubraceArgs,\n  SubraceArgsSchema,\n  SubraceIndexArgsSchema,\n  SubraceOrderField\n} from './args'\n\n@Resolver(Subrace)\nexport class SubraceResolver {\n  @Query(() => [Subrace], {\n    description: 'Gets all subraces, optionally filtered by name and sorted by name.'\n  })\n  async subraces(@Args(() => SubraceArgs) args: SubraceArgs): Promise<Subrace[]> {\n    const validatedArgs = SubraceArgsSchema.parse(args)\n    const query = SubraceModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<SubraceOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: SUBRACE_SORT_FIELD_MAP,\n      defaultSortField: SubraceOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip !== undefined) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit !== undefined) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Subrace, { nullable: true, description: 'Gets a single subrace by index.' })\n  async subrace(@Arg('index', () => String) indexInput: string): Promise<Subrace | null> {\n    const { index } = SubraceIndexArgsSchema.parse({ index: indexInput })\n    return SubraceModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => Race, { nullable: true })\n  async race(@Root() subrace: Subrace): Promise<Race | null> {\n    return resolveSingleReference(subrace.race, RaceModel)\n  }\n\n  @FieldResolver(() => [Trait], { nullable: true })\n  async racial_traits(@Root() subrace: Subrace): Promise<Trait[]> {\n    return resolveMultipleReferences(subrace.racial_traits, TraitModel)\n  }\n}\n@Resolver(SubraceAbilityBonus)\nexport class SubraceAbilityBonusResolver {\n  @FieldResolver(() => AbilityScore, { nullable: true })\n  async ability_score(\n    @Root() subraceAbilityBonus: SubraceAbilityBonus\n  ): Promise<AbilityScore | null> {\n    return resolveSingleReference(subraceAbilityBonus.ability_score, AbilityScoreModel)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/trait/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum TraitOrderField {\n  NAME = 'name'\n}\n\nexport const TRAIT_SORT_FIELD_MAP: Record<TraitOrderField, string> = {\n  [TraitOrderField.NAME]: 'name'\n}\n\nregisterEnumType(TraitOrderField, {\n  name: 'TraitOrderField',\n  description: 'Fields to sort Traits by'\n})\n\n@InputType()\nexport class TraitOrder implements BaseOrderInterface<TraitOrderField> {\n  @Field(() => TraitOrderField)\n  by!: TraitOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => TraitOrder, { nullable: true })\n  then_by?: TraitOrder\n}\n\nexport const TraitOrderSchema: z.ZodType<TraitOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(TraitOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: TraitOrderSchema.optional()\n  })\n)\n\nexport const TraitArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: TraitOrderSchema.optional()\n})\n\nexport const TraitIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class TraitArgs extends BaseFilterArgs {\n  @Field(() => TraitOrder, {\n    nullable: true,\n    description: 'Specify sorting order for traits.'\n  })\n  order?: TraitOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/trait/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { LanguageChoice, ProficiencyChoice } from '@/graphql/2014/common/choiceTypes'\nimport {\n  SpellChoice,\n  SpellChoiceOption,\n  SpellChoiceOptionSet,\n  TraitChoice,\n  TraitChoiceOption,\n  TraitChoiceOptionSet\n} from '@/graphql/2014/types/traitTypes'\nimport { mapLevelObjectToArray } from '@/graphql/2014/utils/helpers'\nimport { resolveLanguageChoice, resolveProficiencyChoice } from '@/graphql/2014/utils/resolvers'\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { LevelValue } from '@/graphql/common/types'\nimport {\n  resolveMultipleReferences,\n  resolveSingleReference,\n  resolveReferenceOptionArray\n} from '@/graphql/utils/resolvers'\nimport DamageTypeModel, { DamageType } from '@/models/2014/damageType'\nimport ProficiencyModel, { Proficiency } from '@/models/2014/proficiency'\nimport RaceModel, { Race } from '@/models/2014/race'\nimport SpellModel from '@/models/2014/spell'\nimport SubraceModel, { Subrace } from '@/models/2014/subrace'\nimport TraitModel, { ActionDamage, Trait, TraitSpecific } from '@/models/2014/trait'\nimport { Choice, OptionsArrayOptionSet } from '@/models/common/choice'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  TRAIT_SORT_FIELD_MAP,\n  TraitArgs,\n  TraitArgsSchema,\n  TraitIndexArgsSchema,\n  TraitOrderField\n} from './args'\n\n@Resolver(Trait)\nexport class TraitResolver {\n  @Query(() => [Trait], {\n    description: 'Gets all traits, optionally filtered by name and sorted by name.'\n  })\n  async traits(@Args(() => TraitArgs) args: TraitArgs): Promise<Trait[]> {\n    const validatedArgs = TraitArgsSchema.parse(args)\n    const query = TraitModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<TraitOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: TRAIT_SORT_FIELD_MAP,\n      defaultSortField: TraitOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip !== undefined) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit !== undefined) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Trait, { nullable: true, description: 'Gets a single trait by index.' })\n  async trait(@Arg('index', () => String) indexInput: string): Promise<Trait | null> {\n    const { index } = TraitIndexArgsSchema.parse({ index: indexInput })\n    return TraitModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => [Proficiency], { nullable: true })\n  async proficiencies(@Root() trait: Trait): Promise<Proficiency[]> {\n    return resolveMultipleReferences(trait.proficiencies, ProficiencyModel)\n  }\n\n  @FieldResolver(() => [Race], { nullable: true })\n  async races(@Root() trait: Trait): Promise<Race[]> {\n    return resolveMultipleReferences(trait.races, RaceModel)\n  }\n\n  @FieldResolver(() => [Subrace], { nullable: true })\n  async subraces(@Root() trait: Trait): Promise<Subrace[]> {\n    return resolveMultipleReferences(trait.subraces, SubraceModel)\n  }\n\n  @FieldResolver(() => Trait, { nullable: true })\n  async parent(@Root() trait: Trait): Promise<Trait | null> {\n    return resolveSingleReference(trait.parent, TraitModel)\n  }\n\n  @FieldResolver(() => LanguageChoice, { nullable: true })\n  async language_options(@Root() trait: Trait): Promise<LanguageChoice | null> {\n    return resolveLanguageChoice(trait.language_options as Choice)\n  }\n\n  @FieldResolver(() => ProficiencyChoice, { nullable: true })\n  async proficiency_choices(@Root() trait: Trait): Promise<ProficiencyChoice | null> {\n    return resolveProficiencyChoice(trait.proficiency_choices)\n  }\n}\n\n// Separate resolver for nested TraitSpecific type\n@Resolver(TraitSpecific)\nexport class TraitSpecificResolver {\n  @FieldResolver(() => DamageType, { nullable: true })\n  async damage_type(@Root() traitSpecific: TraitSpecific): Promise<DamageType | null> {\n    return resolveSingleReference(traitSpecific.damage_type, DamageTypeModel)\n  }\n\n  @FieldResolver(() => TraitChoice, { nullable: true })\n  async subtrait_options(@Root() traitSpecific: TraitSpecific) {\n    return resolveTraitChoice(traitSpecific.subtrait_options)\n  }\n\n  @FieldResolver(() => SpellChoice, { nullable: true })\n  async spell_options(@Root() traitSpecific: TraitSpecific) {\n    return resolveSpellChoice(traitSpecific.spell_options)\n  }\n}\n\n@Resolver(ActionDamage)\nexport class ActionDamageResolver {\n  @FieldResolver(() => [LevelValue], {\n    nullable: true,\n    description: 'Damage scaling based on character level, transformed from the raw data object.'\n  })\n  async damage_at_character_level(\n    @Root() actionDamage: ActionDamage\n  ): Promise<LevelValue[] | null> {\n    return mapLevelObjectToArray(actionDamage.damage_at_character_level)\n  }\n}\n\nasync function resolveTraitChoice(\n  choiceData: Choice | undefined | null\n): Promise<TraitChoice | null> {\n  if (!choiceData) {\n    return null\n  }\n\n  const optionsArraySet = choiceData.from as OptionsArrayOptionSet\n  const gqlEmbeddedOptions = await resolveReferenceOptionArray(\n    optionsArraySet,\n    TraitModel,\n    (item, optionType) => ({ option_type: optionType, item }) as TraitChoiceOption\n  )\n\n  const gqlOptionSet: TraitChoiceOptionSet = {\n    option_set_type: choiceData.from.option_set_type,\n    options: gqlEmbeddedOptions\n  }\n\n  return {\n    choose: choiceData.choose,\n    type: choiceData.type,\n    from: gqlOptionSet\n  }\n}\n\nasync function resolveSpellChoice(\n  choiceData: Choice | undefined | null\n): Promise<SpellChoice | null> {\n  if (!choiceData) {\n    return null\n  }\n\n  const optionsArraySet = choiceData.from as OptionsArrayOptionSet\n  const gqlEmbeddedOptions = await resolveReferenceOptionArray(\n    optionsArraySet,\n    SpellModel,\n    (item, optionType) => ({ option_type: optionType, item }) as SpellChoiceOption\n  )\n\n  const gqlOptionSet: SpellChoiceOptionSet = {\n    option_set_type: choiceData.from.option_set_type,\n    options: gqlEmbeddedOptions\n  }\n\n  return {\n    choose: choiceData.choose,\n    type: choiceData.type,\n    from: gqlOptionSet\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/weaponProperty/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum WeaponPropertyOrderField {\n  NAME = 'name'\n}\n\nexport const WEAPON_PROPERTY_SORT_FIELD_MAP: Record<WeaponPropertyOrderField, string> = {\n  [WeaponPropertyOrderField.NAME]: 'name'\n}\n\nregisterEnumType(WeaponPropertyOrderField, {\n  name: 'WeaponPropertyOrderField',\n  description: 'Fields to sort Weapon Properties by'\n})\n\n@InputType()\nexport class WeaponPropertyOrder implements BaseOrderInterface<WeaponPropertyOrderField> {\n  @Field(() => WeaponPropertyOrderField)\n  by!: WeaponPropertyOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => WeaponPropertyOrder, { nullable: true })\n  then_by?: WeaponPropertyOrder\n}\n\nexport const WeaponPropertyOrderSchema: z.ZodType<WeaponPropertyOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(WeaponPropertyOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: WeaponPropertyOrderSchema.optional()\n  })\n)\n\nexport const WeaponPropertyArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: WeaponPropertyOrderSchema.optional()\n})\n\nexport const WeaponPropertyIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class WeaponPropertyArgs extends BaseFilterArgs {\n  @Field(() => WeaponPropertyOrder, {\n    nullable: true,\n    description: 'Specify sorting order for weapon properties.'\n  })\n  order?: WeaponPropertyOrder\n}\n"
  },
  {
    "path": "src/graphql/2014/resolvers/weaponProperty/resolver.ts",
    "content": "import { Arg, Args, Query, Resolver } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport WeaponPropertyModel, { WeaponProperty } from '@/models/2014/weaponProperty'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  WEAPON_PROPERTY_SORT_FIELD_MAP,\n  WeaponPropertyArgs,\n  WeaponPropertyArgsSchema,\n  WeaponPropertyIndexArgsSchema,\n  WeaponPropertyOrderField\n} from './args'\n\n@Resolver(WeaponProperty)\nexport class WeaponPropertyResolver {\n  @Query(() => [WeaponProperty], {\n    description: 'Gets all weapon properties, optionally filtered by name and sorted by name.'\n  })\n  async weaponProperties(\n    @Args(() => WeaponPropertyArgs) args: WeaponPropertyArgs\n  ): Promise<WeaponProperty[]> {\n    const validatedArgs = WeaponPropertyArgsSchema.parse(args)\n    const query = WeaponPropertyModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<WeaponPropertyOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: WEAPON_PROPERTY_SORT_FIELD_MAP,\n      defaultSortField: WeaponPropertyOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => WeaponProperty, {\n    nullable: true,\n    description: 'Gets a single weapon property by index.'\n  })\n  async weaponProperty(\n    @Arg('index', () => String) indexInput: string\n  ): Promise<WeaponProperty | null> {\n    const { index } = WeaponPropertyIndexArgsSchema.parse({ index: indexInput })\n    return WeaponPropertyModel.findOne({ index }).lean()\n  }\n}\n"
  },
  {
    "path": "src/graphql/2014/types/backgroundTypes.ts",
    "content": "import { Field, Int, ObjectType } from 'type-graphql'\n\nimport { Alignment } from '@/models/2014/alignment'\n\n// --- Background Ideal Choice Types ---\n@ObjectType({ description: 'Represents a single ideal option for a background.' })\nexport class IdealOption {\n  @Field(() => String, { description: 'The type of the ideal option (e.g., ideal).' })\n  option_type!: string\n\n  @Field(() => String, { description: 'The description of the ideal.' })\n  desc!: string\n\n  @Field(() => [Alignment], { description: 'Alignments associated with this ideal.' })\n  alignments!: Alignment[]\n}\n\n@ObjectType({ description: 'Represents a set of ideal options for a background.' })\nexport class IdealOptionSet {\n  @Field(() => String, { description: 'The type of the ideal option set (e.g., options_array).' })\n  option_set_type!: string\n\n  @Field(() => [IdealOption], { description: 'The list of ideal options available.' })\n  options!: IdealOption[]\n}\n\n@ObjectType({ description: 'Represents the choice structure for background ideals.' })\nexport class IdealChoice {\n  @Field(() => Int, { description: 'The number of ideals to choose from this list.' })\n  choose!: number\n\n  @Field(() => String, { description: 'The type of choice (e.g., ideals).' })\n  type!: string\n\n  @Field(() => IdealOptionSet, { description: 'The set of ideal options available.' })\n  from!: IdealOptionSet\n}\n"
  },
  {
    "path": "src/graphql/2014/types/featureTypes.ts",
    "content": "import { createUnionType } from 'type-graphql'\n\nimport { FeaturePrerequisite, LevelPrerequisite, SpellPrerequisite } from '@/models/2014/feature'\n\nexport const FeaturePrerequisiteUnion = createUnionType({\n  name: 'FeaturePrerequisiteUnion',\n  types: () => [LevelPrerequisite, FeaturePrerequisite, SpellPrerequisite] as const,\n  resolveType: (value) => {\n    if (value.type === 'level') {\n      return LevelPrerequisite\n    }\n    if (value.type === 'feature') {\n      return FeaturePrerequisite\n    }\n    if (value.type === 'spell') {\n      return SpellPrerequisite\n    }\n    console.warn('Could not resolve type for FeaturePrerequisiteUnion:', value)\n    throw new Error('Could not resolve type for FeaturePrerequisiteUnion')\n  }\n})\n"
  },
  {
    "path": "src/graphql/2014/types/monsterTypes.ts",
    "content": "import { createUnionType, Field, Int, ObjectType } from 'type-graphql'\n\nimport {\n  ArmorClassArmor,\n  ArmorClassCondition,\n  ArmorClassDex,\n  ArmorClassNatural,\n  ArmorClassSpell\n} from '@/models/2014/monster'\nimport { Damage } from '@/models/common/damage'\nimport { DifficultyClass } from '@/models/common/difficultyClass'\n\n// --- Breath Choice Types ---\n@ObjectType({ description: 'A single breath option within a breath choice' })\nexport class BreathChoiceOption {\n  @Field(() => String, { description: 'The type of option (e.g., breath).' })\n  option_type!: string\n\n  @Field(() => String, { description: 'The name of the breath option.' })\n  name!: string\n\n  @Field(() => DifficultyClass, { description: 'The difficulty class for the breath.' })\n  dc!: DifficultyClass\n\n  @Field(() => [Damage], { nullable: true, description: 'The damage dealt by the breath.' })\n  damage?: Damage[]\n}\n\n@ObjectType({ description: 'A set of breath options to choose from' })\nexport class BreathChoiceOptionSet {\n  @Field(() => String, { description: 'The type of option set.' })\n  option_set_type!: string\n\n  @Field(() => [BreathChoiceOption], { description: 'The available breath options.' })\n  options!: BreathChoiceOption[]\n}\n\n@ObjectType({ description: 'A choice of breath options for a monster action' })\nexport class BreathChoice {\n  @Field(() => Int, { description: 'Number of breath options to choose.' })\n  choose!: number\n\n  @Field(() => String, { description: 'Type of breath options to choose from.' })\n  type!: string\n\n  @Field(() => BreathChoiceOptionSet, { description: 'The options to choose from.' })\n  from!: BreathChoiceOptionSet\n\n  @Field(() => String, { nullable: true, description: 'Description of the breath choice.' })\n  desc?: string\n}\n\n// --- Damage Choice Types ---\n@ObjectType({ description: 'A single damage option in a damage choice' })\nexport class DamageChoiceOption {\n  @Field(() => String, { description: 'The type of option.' })\n  option_type!: string\n\n  @Field(() => Damage, { description: 'The damage for this option.' })\n  damage!: Damage\n}\n\n@ObjectType({ description: 'A set of damage options' })\nexport class DamageChoiceOptionSet {\n  @Field(() => String, { description: 'The type of option set.' })\n  option_set_type!: string\n\n  @Field(() => [DamageChoiceOption], { description: 'The options in this set.' })\n  options!: DamageChoiceOption[]\n}\n\n@ObjectType({ description: 'A choice of damage options' })\nexport class DamageChoice {\n  @Field(() => Number, { description: 'The number of options to choose.' })\n  choose!: number\n\n  @Field(() => String, { description: 'The type of choice.' })\n  type!: string\n\n  @Field(() => DamageChoiceOptionSet, { description: 'The options to choose from.' })\n  from!: DamageChoiceOptionSet\n\n  @Field(() => String, { nullable: true, description: 'The description of the choice.' })\n  desc?: string\n}\n\n// --- Action Option Choice Types ---\n@ObjectType({ description: 'A single action option within a choice' })\nexport class ActionChoiceOption {\n  @Field(() => String, { description: 'The type of option.' })\n  option_type!: string\n\n  @Field(() => String, { description: 'The name of the action.' })\n  action_name!: string\n\n  @Field(() => Int, { description: 'Number of times the action can be used.' })\n  count!: number\n\n  @Field(() => String, { description: 'The type of action.' })\n  type!: 'melee' | 'ranged' | 'ability' | 'magic'\n\n  @Field(() => String, { nullable: true, description: 'Additional notes about the action.' })\n  notes?: string\n}\n\n@ObjectType({ description: 'A multiple action option containing a set of actions' })\nexport class MultipleActionChoiceOption {\n  @Field(() => String, { description: 'The type of option.' })\n  option_type!: string\n\n  @Field(() => [ActionChoiceOption], { description: 'The set of actions in this option.' })\n  items!: ActionChoiceOption[]\n}\n\n@ObjectType({ description: 'A set of action options to choose from' })\nexport class ActionChoiceOptionSet {\n  @Field(() => String, { description: 'The type of option set.' })\n  option_set_type!: string\n\n  @Field(() => [ActionOptionUnion], { description: 'The available options.' })\n  options!: Array<ActionChoiceOption | MultipleActionChoiceOption>\n}\n\n@ObjectType({ description: 'A choice of actions for a monster' })\nexport class ActionChoice {\n  @Field(() => Int, { description: 'Number of actions to choose.' })\n  choose!: number\n\n  @Field(() => String, { description: 'Type of actions to choose from.' })\n  type!: string\n\n  @Field(() => ActionChoiceOptionSet, { description: 'The options to choose from.' })\n  from!: ActionChoiceOptionSet\n\n  @Field(() => String, { nullable: true, description: 'Description of the action choice.' })\n  desc?: string\n}\n\n// --- Unions ---\n\nexport const ActionOptionUnion = createUnionType({\n  name: 'ActionOptionUnion',\n  types: () => [ActionChoiceOption, MultipleActionChoiceOption],\n  resolveType(value) {\n    if ('items' in value) {\n      return MultipleActionChoiceOption\n    }\n    return ActionChoiceOption\n  }\n})\n\nexport const DamageOrDamageChoiceUnion = createUnionType({\n  name: 'DamageOrDamageChoice',\n  types: () => [Damage, DamageChoice],\n  resolveType: (value: Damage | DamageChoice) => {\n    if ('choose' in value) {\n      return DamageChoice\n    }\n    return Damage\n  }\n})\n\nexport const MonsterArmorClassUnion = createUnionType({\n  name: 'MonsterArmorClass',\n  types: () =>\n    [\n      ArmorClassDex,\n      ArmorClassNatural,\n      ArmorClassArmor,\n      ArmorClassSpell,\n      ArmorClassCondition\n    ] as const,\n  resolveType: (value: any) => {\n    if (value == null || typeof value.type !== 'string') {\n      console.warn('Cannot resolve MonsterArmorClass: type field is missing or invalid', value)\n      throw new Error('Cannot resolve MonsterArmorClass: type field is missing or invalid')\n    }\n    switch (value.type) {\n      case 'dex':\n        return ArmorClassDex\n      case 'natural':\n        return ArmorClassNatural\n      case 'armor':\n        return ArmorClassArmor\n      case 'spell':\n        return ArmorClassSpell\n      case 'condition':\n        return ArmorClassCondition\n      default:\n        console.warn('Could not resolve type for MonsterArmorClassUnion:', value)\n        throw new Error(\n          'Could not resolve type for MonsterArmorClassUnion: Unknown type ' + value.type\n        )\n    }\n  }\n})\n"
  },
  {
    "path": "src/graphql/2014/types/proficiencyTypes.ts",
    "content": "import { createUnionType } from 'type-graphql'\n\nimport { AbilityScore } from '@/models/2014/abilityScore'\nimport { Equipment } from '@/models/2014/equipment'\nimport { EquipmentCategory } from '@/models/2014/equipmentCategory'\nimport { Skill } from '@/models/2014/skill'\n\nexport const ProficiencyReference = createUnionType({\n  name: 'ProficiencyReference',\n  types: () => [Equipment, EquipmentCategory, AbilityScore, Skill] as const,\n  resolveType: (value) => {\n    if ('equipment' in value) {\n      return EquipmentCategory\n    }\n    if ('full_name' in value) {\n      return AbilityScore\n    }\n    if ('desc' in value && Array.isArray(value.desc)) {\n      return Skill\n    }\n    return Equipment\n  }\n})\n"
  },
  {
    "path": "src/graphql/2014/types/startingEquipment/choice.ts",
    "content": "import { Field, Int, ObjectType } from 'type-graphql'\n\nimport { EquipmentCategorySet } from './common'\nimport { EquipmentOptionSet, StartingEquipmentFromUnion } from './optionSet'\n\n@ObjectType({ description: 'Represents a choice for starting equipment.' })\nexport class StartingEquipmentChoice {\n  @Field(() => Int, { description: 'The number of items or options to choose.' })\n  choose!: number\n\n  @Field(() => String, {\n    nullable: true,\n    description: 'A description of the choice presented to the user.'\n  })\n  desc?: string // desc can be optional based on some data models\n\n  @Field(() => String, { description: \"The type of choice, e.g., 'equipment'.\" })\n  type!: string\n\n  // This will use a forward reference to StartingEquipmentFromUnion defined in optionSet.ts\n  @Field(() => StartingEquipmentFromUnion, {\n    description: 'The set of options or category to choose from.'\n  })\n  from!: EquipmentCategorySet | EquipmentOptionSet\n}\n"
  },
  {
    "path": "src/graphql/2014/types/startingEquipment/common.ts",
    "content": "import { createUnionType, Field, Int, ObjectType } from 'type-graphql'\n\nimport { Equipment } from '@/models/2014/equipment'\nimport { EquipmentCategory } from '@/models/2014/equipmentCategory'\nimport { Proficiency } from '@/models/2014/proficiency'\n\n@ObjectType({\n  description: 'A prerequisite for an equipment option, typically requiring a specific proficiency.'\n})\nexport class ProficiencyPrerequisite {\n  @Field(() => String, { description: \"The type of prerequisite, e.g., 'proficiency'.\" })\n  type!: string\n\n  @Field(() => Proficiency, {\n    description: 'The specific proficiency required.'\n  })\n  proficiency!: Proficiency\n}\n\n@ObjectType({ description: 'Represents a specific piece of equipment with a quantity.' })\nexport class CountedReferenceOption {\n  @Field(() => String, { description: \"The type of this option, e.g., 'counted_reference'.\" })\n  option_type!: string // This helps in discriminating union types if used directly\n\n  @Field(() => Int, { description: 'The quantity of the equipment.' })\n  count!: number\n\n  @Field(() => Equipment, { description: 'The referenced equipment item.' })\n  of!: Equipment\n\n  @Field(() => [ProficiencyPrerequisite], {\n    nullable: true,\n    description: 'Prerequisites for choosing this option.'\n  })\n  prerequisites?: ProficiencyPrerequisite[]\n}\n\n// Definition for EquipmentCategorySet, moved from optionSet.ts and placed before its usage\n@ObjectType({\n  description: 'A set of equipment choices derived directly from an equipment category.'\n})\nexport class EquipmentCategorySet {\n  @Field(() => String, {\n    description: \"Indicates the type of option set, e.g., 'equipment_category'.\"\n  })\n  option_set_type!: string\n\n  @Field(() => EquipmentCategory, {\n    description: 'The equipment category to choose from.'\n  })\n  equipment_category!: EquipmentCategory\n}\n\n// Describes the details of a choice that is specifically from an equipment category.\n// This is used inside EquipmentCategoryChoiceOption.\n@ObjectType({ description: 'Details of a choice limited to an equipment category.' })\nexport class EquipmentCategoryChoice {\n  @Field(() => Int, { description: 'Number of items to choose from the category.' })\n  choose!: number\n\n  @Field(() => String, { nullable: true, description: 'An optional description for this choice.' })\n  desc?: string\n\n  @Field(() => String, { description: \"Type of choice, e.g., 'equipment'.\" })\n  type!: string\n\n  @Field(() => EquipmentCategorySet, {\n    description: 'The equipment category to choose from.'\n  })\n  from!: EquipmentCategorySet\n}\n\n@ObjectType({ description: 'An option that represents a choice from a single equipment category.' })\nexport class EquipmentCategoryChoiceOption {\n  @Field(() => String, {\n    description: \"The type of this option, e.g., 'choice' or 'equipment_category_choice'.\"\n  })\n  option_type!: string // Data might say 'choice', resolver will map to this type if structure matches\n\n  @Field(() => EquipmentCategoryChoice, {\n    description: 'The details of the choice from an equipment category.'\n  })\n  choice!: EquipmentCategoryChoice\n}\n\n// This union represents the types of items that can be part of a 'multiple' bundle.\nexport const MultipleItemUnion = createUnionType({\n  name: 'MultipleItemUnion',\n  types: () => [CountedReferenceOption, EquipmentCategoryChoiceOption] as const,\n  resolveType: (value) => {\n    if (value.option_type === 'counted_reference') {\n      return CountedReferenceOption\n    }\n    if (value.option_type === 'choice' || value.option_type === 'equipment_category_choice') {\n      return EquipmentCategoryChoiceOption\n    }\n    return undefined\n  }\n})\n\n@ObjectType({\n  description: 'Represents a bundle of multiple equipment items or equipment category choices.'\n})\nexport class MultipleItemsOption {\n  @Field(() => String, { description: \"The type of this option, e.g., 'multiple'.\" })\n  option_type!: string\n\n  @Field(() => [MultipleItemUnion], {\n    description: 'The list of items or category choices included in this bundle.'\n  })\n  items!: Array<CountedReferenceOption | EquipmentCategoryChoiceOption>\n}\n"
  },
  {
    "path": "src/graphql/2014/types/startingEquipment/index.ts",
    "content": "export * from './choice'\nexport * from './common'\nexport * from './optionSet'\n"
  },
  {
    "path": "src/graphql/2014/types/startingEquipment/optionSet.ts",
    "content": "import { createUnionType, Field, ObjectType } from 'type-graphql'\n\nimport {\n  CountedReferenceOption,\n  EquipmentCategoryChoiceOption,\n  EquipmentCategorySet,\n  MultipleItemsOption\n} from './common'\n\n@ObjectType({ description: 'A set of explicitly listed equipment options.' })\nexport class EquipmentOptionSet {\n  @Field(() => String, { description: \"Indicates the type of option set, e.g., 'options_array'.\" })\n  option_set_type!: string\n\n  @Field(() => [EquipmentOptionUnion], { description: 'A list of specific equipment options.' })\n  options!: Array<CountedReferenceOption | EquipmentCategoryChoiceOption | MultipleItemsOption>\n}\n\n// Union for the `from` field of StartingEquipmentChoice\nexport const StartingEquipmentFromUnion = createUnionType({\n  name: 'StartingEquipmentFromUnion',\n  types: () => [EquipmentCategorySet, EquipmentOptionSet] as const,\n  resolveType: (value) => {\n    if (value.option_set_type === 'equipment_category') {\n      return EquipmentCategorySet\n    }\n    if (value.option_set_type === 'options_array') {\n      return EquipmentOptionSet\n    }\n    return undefined\n  }\n})\n\n// Union for items within EquipmentOptionSet.options\nexport const EquipmentOptionUnion = createUnionType({\n  name: 'EquipmentOptionUnion',\n  types: () =>\n    [CountedReferenceOption, EquipmentCategoryChoiceOption, MultipleItemsOption] as const,\n  resolveType: (value) => {\n    if (value.option_type === 'counted_reference') {\n      return CountedReferenceOption\n    }\n    if (value.option_type === 'choice' || value.option_type === 'equipment_category_choice') {\n      return EquipmentCategoryChoiceOption\n    }\n    if (value.option_type === 'multiple') {\n      return MultipleItemsOption\n    }\n    return undefined\n  }\n})\n"
  },
  {
    "path": "src/graphql/2014/types/subclassTypes.ts",
    "content": "import { createUnionType } from 'type-graphql'\n\nimport { Feature } from '@/models/2014/feature'\nimport { Level } from '@/models/2014/level'\n\nexport const SubclassSpellPrerequisiteUnion = createUnionType({\n  name: 'SubclassSpellPrerequisite',\n  types: () => [Level, Feature] as const,\n  resolveType: (value) => {\n    if ('prof_bonus' in value || 'spellcasting' in value || 'features' in value) {\n      return Level\n    }\n    if ('subclass' in value || 'feature_specific' in value || 'prerequisites' in value) {\n      return Feature\n    }\n\n    console.warn('Could not reliably resolve type for SubclassSpellPrerequisiteUnion:', value)\n    throw new Error('Could not resolve type for SubclassSpellPrerequisiteUnion')\n  }\n})\n"
  },
  {
    "path": "src/graphql/2014/types/traitTypes.ts",
    "content": "import { Field, Int, ObjectType } from 'type-graphql'\n\nimport { Spell } from '@/models/2014/spell'\nimport { Trait } from '@/models/2014/trait'\n\n@ObjectType({ description: 'Represents a reference to a Trait within a choice option set.' })\nexport class TraitChoiceOption {\n  @Field(() => String, { description: 'The type of this option (e.g., \"reference\").' })\n  option_type!: string\n\n  @Field(() => Trait, {\n    description: 'The resolved Trait object.'\n  })\n  item!: Trait\n}\n\n@ObjectType({ description: 'Represents a set of Trait options for a choice.' })\nexport class TraitChoiceOptionSet {\n  @Field(() => String, {\n    description: 'The type of the option set (e.g., resource_list, options_array).'\n  })\n  option_set_type!: string\n\n  @Field(() => [TraitChoiceOption], { description: 'The list of Trait options available.' })\n  options!: TraitChoiceOption[]\n}\n\n@ObjectType({ description: 'Represents a choice from a list of Traits.' })\nexport class TraitChoice {\n  @Field(() => Int, { description: 'The number of Traits to choose from this list.' })\n  choose!: number\n\n  @Field(() => String, { description: 'The type of choice (e.g., subtraits).' })\n  type!: string\n\n  @Field(() => TraitChoiceOptionSet, { description: 'The set of Trait options available.' })\n  from!: TraitChoiceOptionSet\n}\n\n@ObjectType({ description: 'Represents a reference to a Spell within a choice option set.' })\nexport class SpellChoiceOption {\n  @Field(() => String, { description: 'The type of this option (e.g., \"reference\").' })\n  option_type!: string\n\n  @Field(() => Spell, {\n    description: 'The resolved Spell object.'\n  })\n  item!: Spell\n}\n\n@ObjectType({ description: 'Represents a set of Spell options for a choice.' })\nexport class SpellChoiceOptionSet {\n  @Field(() => String, {\n    description: 'The type of the option set (e.g., resource_list, options_array).'\n  })\n  option_set_type!: string\n\n  @Field(() => [SpellChoiceOption], { description: 'The list of Spell options available.' })\n  options!: SpellChoiceOption[]\n}\n\n@ObjectType({ description: 'Represents a choice from a list of Spells.' })\nexport class SpellChoice {\n  @Field(() => Int, { description: 'The number of Spells to choose from this list.' })\n  choose!: number\n\n  @Field(() => String, { description: 'The type of choice (e.g., spells).' })\n  type!: string\n\n  @Field(() => SpellChoiceOptionSet, { description: 'The set of Spell options available.' })\n  from!: SpellChoiceOptionSet\n}\n"
  },
  {
    "path": "src/graphql/2014/utils/helpers.ts",
    "content": "import { LevelValue } from '@/graphql/common/types'\n\n/**\n * Converts a Record<number, string> or Record<string, string> (where keys are number strings)\n * into an array of { level: number, value: string } objects, sorted by level.\n * Returns null if the input is invalid or empty.\n */\nexport const mapLevelObjectToArray = (\n  data: Record<number, string> | Record<string, string> | undefined\n): LevelValue[] | null => {\n  if (!data || typeof data !== 'object') {\n    return null\n  }\n\n  const levelValueArray: LevelValue[] = []\n  const stringData = data as Record<string, string>\n  for (const levelKey in stringData) {\n    if (Object.prototype.hasOwnProperty.call(stringData, levelKey)) {\n      const level = parseInt(levelKey, 10)\n      const value = stringData[levelKey]\n\n      if (!isNaN(level) && typeof value === 'string') {\n        levelValueArray.push({ level, value })\n      }\n    }\n  }\n  levelValueArray.sort((a, b) => a.level - b.level)\n  return levelValueArray.length > 0 ? levelValueArray : null\n}\n\n/**\n * Normalizes a count value (which can be a string or number) to a number.\n * Uses parseInt with radix 10 for strings.\n */\nexport function normalizeCount(count: string | number): number {\n  if (typeof count === 'string') {\n    const num = parseInt(count, 10)\n    return isNaN(num) ? 0 : num\n  }\n  return count\n}\n"
  },
  {
    "path": "src/graphql/2014/utils/resolvers.ts",
    "content": "import {\n  LanguageChoice,\n  LanguageChoiceOption,\n  LanguageChoiceOptionSet,\n  ProficiencyChoice,\n  ProficiencyChoiceOption,\n  ProficiencyChoiceOptionSet\n} from '@/graphql/2014/common/choiceTypes'\nimport { resolveSingleReference, resolveReferenceOptionArray } from '@/graphql/utils/resolvers'\nimport LanguageModel, { Language } from '@/models/2014/language'\nimport ProficiencyModel, { Proficiency } from '@/models/2014/proficiency'\nimport {\n  Choice,\n  ChoiceOption,\n  OptionsArrayOptionSet,\n  ReferenceOption\n} from '@/models/common/choice'\n\nexport async function resolveLanguageChoice(\n  choiceData: Choice | null\n): Promise<LanguageChoice | null> {\n  const gqlEmbeddedOptions: LanguageChoiceOption[] = []\n\n  if (!choiceData) {\n    return null\n  }\n\n  if (choiceData.from.option_set_type === 'resource_list') {\n    const allItems = (await LanguageModel.find({}).lean()) as Language[]\n    for (const item of allItems) {\n      gqlEmbeddedOptions.push({\n        option_type: 'reference',\n        item: item\n      })\n    }\n  } else if (choiceData.from.option_set_type === 'options_array') {\n    const optionsArraySet = choiceData.from as OptionsArrayOptionSet\n    const resolvedOptions = await resolveReferenceOptionArray(\n      optionsArraySet,\n      LanguageModel,\n      (item, optionType) => ({ option_type: optionType, item }) as LanguageChoiceOption\n    )\n    gqlEmbeddedOptions.push(...resolvedOptions)\n  }\n\n  const gqlOptionSet: LanguageChoiceOptionSet = {\n    option_set_type: choiceData.from.option_set_type,\n    options: gqlEmbeddedOptions\n  }\n\n  return {\n    choose: choiceData.choose,\n    type: choiceData.type,\n    from: gqlOptionSet\n  }\n}\n\nexport async function resolveProficiencyChoice(\n  choiceData: Choice | undefined | null\n): Promise<ProficiencyChoice | null> {\n  if (!choiceData || !choiceData.type) {\n    return null\n  }\n\n  const gqlEmbeddedOptions: ProficiencyChoiceOption[] = []\n\n  const optionsArraySet = choiceData.from as OptionsArrayOptionSet\n  for (const dbOption of optionsArraySet.options) {\n    if (dbOption.option_type === 'choice') {\n      // For nested choices, use ChoiceOption\n      const choiceOpt = dbOption as ChoiceOption\n      const nestedChoice = await resolveProficiencyChoice(choiceOpt.choice)\n      if (nestedChoice) {\n        gqlEmbeddedOptions.push({\n          option_type: choiceOpt.option_type,\n          item: nestedChoice\n        })\n      }\n    } else {\n      // Handle regular proficiency reference\n      const dbRefOpt = dbOption as ReferenceOption\n      const resolvedItem = await resolveSingleReference(dbRefOpt.item, ProficiencyModel)\n      if (resolvedItem !== null) {\n        gqlEmbeddedOptions.push({\n          option_type: dbRefOpt.option_type,\n          item: resolvedItem as Proficiency\n        })\n      }\n    }\n  }\n\n  const gqlOptionSet: ProficiencyChoiceOptionSet = {\n    option_set_type: choiceData.from.option_set_type,\n    options: gqlEmbeddedOptions\n  }\n\n  return {\n    choose: choiceData.choose,\n    type: choiceData.type,\n    from: gqlOptionSet,\n    desc: choiceData.desc\n  }\n}\n\nexport async function resolveProficiencyChoiceArray(\n  choices: Choice[] | undefined | null\n): Promise<ProficiencyChoice[]> {\n  if (!choices || !Array.isArray(choices)) {\n    return []\n  }\n\n  const resolvedChoices: ProficiencyChoice[] = []\n  for (const choice of choices) {\n    const resolvedChoice = await resolveProficiencyChoice(choice)\n    if (resolvedChoice) {\n      resolvedChoices.push(resolvedChoice)\n    }\n  }\n\n  return resolvedChoices\n}\n"
  },
  {
    "path": "src/graphql/2014/utils/startingEquipmentResolver.ts",
    "content": "import EquipmentModel, { Equipment } from '@/models/2014/equipment'\nimport EquipmentCategoryModel, { EquipmentCategory } from '@/models/2014/equipmentCategory'\nimport ProficiencyModel, { Proficiency } from '@/models/2014/proficiency'\nimport {\n  Choice,\n  ChoiceOption,\n  CountedReferenceOption,\n  EquipmentCategoryOptionSet,\n  MultipleOption,\n  OptionsArrayOptionSet,\n  OptionSet\n} from '@/models/common/choice'\n\nimport {\n  CountedReferenceOption as ResolvedCountedReferenceOption,\n  EquipmentCategoryChoiceOption,\n  EquipmentCategorySet,\n  EquipmentOptionSet,\n  MultipleItemsOption,\n  ProficiencyPrerequisite as ResolvedProficiencyPrerequisite,\n  StartingEquipmentChoice\n} from '../types/startingEquipment'\n\ninterface ProficiencyPrerequisite {\n  type: string\n  proficiency: { index: string; name: string; url: string }\n}\n\n// --- Main Resolver Function ---\nexport async function resolveStartingEquipmentChoices(\n  choices: Choice[] | undefined | null\n): Promise<StartingEquipmentChoice[]> {\n  if (!choices) {\n    return []\n  }\n  const resolvedChoices: StartingEquipmentChoice[] = []\n  for (const choice of choices) {\n    const resolvedChoice = await resolveStartingEquipmentChoice(choice)\n    if (resolvedChoice) {\n      resolvedChoices.push(resolvedChoice)\n    }\n  }\n  return resolvedChoices\n}\n\n// --- Helper to map a single DB Choice to GraphQL StartingEquipmentChoice ---\nasync function resolveStartingEquipmentChoice(\n  choice: Choice\n): Promise<StartingEquipmentChoice | null> {\n  const resolvedFrom = await resolveStartingEquipmentOptionSet(choice.from as OptionSet)\n  if (!resolvedFrom) {\n    return null\n  }\n\n  return {\n    choose: choice.choose,\n    desc: choice.desc,\n    type: choice.type,\n    from: resolvedFrom\n  }\n}\n\n// Maps the 'from' part of a DB choice to either GQL EquipmentCategorySet or EquipmentOptionSet\nasync function resolveStartingEquipmentOptionSet(\n  optionSet: OptionSet\n): Promise<EquipmentCategorySet | EquipmentOptionSet | null> {\n  if (optionSet.option_set_type === 'equipment_category') {\n    const equipmentCategoryOptionSet = optionSet as EquipmentCategoryOptionSet\n    const category = await EquipmentCategoryModel.findOne({\n      index: equipmentCategoryOptionSet.equipment_category.index\n    }).lean()\n    if (!category) return null\n\n    return {\n      option_set_type: equipmentCategoryOptionSet.option_set_type,\n      equipment_category: category as EquipmentCategory\n    } as EquipmentCategorySet\n  } else if (optionSet.option_set_type === 'options_array') {\n    return await resolveEquipmentOptionSet(optionSet as OptionsArrayOptionSet)\n  }\n  return null\n}\n\nasync function resolveEquipmentOptionSet(\n  optionSet: OptionsArrayOptionSet\n): Promise<EquipmentOptionSet | null> {\n  const resolvedOptions: Array<\n    ResolvedCountedReferenceOption | EquipmentCategoryChoiceOption | MultipleItemsOption\n  > = []\n  for (const option of optionSet.options) {\n    const resolvedOption = await resolveEquipmentOptionUnion(\n      option as CountedReferenceOption | ChoiceOption | MultipleOption\n    )\n    if (resolvedOption) {\n      resolvedOptions.push(resolvedOption)\n    }\n  }\n\n  return {\n    option_set_type: optionSet.option_set_type,\n    options: resolvedOptions\n  } as EquipmentOptionSet\n}\n\nasync function resolveEquipmentOptionUnion(\n  option: CountedReferenceOption | ChoiceOption | MultipleOption\n): Promise<\n  ResolvedCountedReferenceOption | EquipmentCategoryChoiceOption | MultipleItemsOption | null\n> {\n  if (!option.option_type) return null\n\n  switch (option.option_type) {\n    case 'counted_reference':\n      return resolveCountedReferenceOption(option as CountedReferenceOption)\n    case 'choice':\n      return resolveEquipmentCategoryChoiceOption(option as ChoiceOption)\n    case 'multiple':\n      return resolveMultipleItemsOption(option as MultipleOption)\n    default:\n      console.warn(`Unknown option.option_type: ${option.option_type}`)\n      return null\n  }\n}\n\nasync function resolveProficiencyPrerequisites(\n  prerequisites: ProficiencyPrerequisite[] | undefined\n): Promise<ResolvedProficiencyPrerequisite[]> {\n  if (!prerequisites || prerequisites.length === 0) {\n    return []\n  }\n  const resolvedPrerequisites: ResolvedProficiencyPrerequisite[] = []\n  for (const prereq of prerequisites) {\n    const proficiency = await ProficiencyModel.findOne({ index: prereq.proficiency.index }).lean()\n    if (proficiency) {\n      resolvedPrerequisites.push({\n        type: prereq.type,\n        proficiency: proficiency as Proficiency\n      })\n    }\n  }\n  return resolvedPrerequisites\n}\n\nasync function resolveCountedReferenceOption(\n  countedReferenceOption: CountedReferenceOption\n): Promise<ResolvedCountedReferenceOption | null> {\n  if (!countedReferenceOption.of.index) return null\n\n  const equipment = await EquipmentModel.findOne({ index: countedReferenceOption.of.index }).lean()\n  if (!equipment) return null\n\n  const resolvedPrerequisites = await resolveProficiencyPrerequisites(\n    countedReferenceOption.prerequisites as ProficiencyPrerequisite[] | undefined\n  )\n\n  return {\n    option_type: countedReferenceOption.option_type,\n    count: countedReferenceOption.count,\n    of: equipment as Equipment,\n    prerequisites: resolvedPrerequisites.length > 0 ? resolvedPrerequisites : undefined\n  } as ResolvedCountedReferenceOption\n}\n\nasync function resolveEquipmentCategoryChoiceOption(\n  choiceOption: ChoiceOption\n): Promise<EquipmentCategoryChoiceOption | null> {\n  const nestedChoice = choiceOption.choice\n  const nestedFrom = nestedChoice.from as EquipmentCategoryOptionSet\n\n  if (nestedFrom.option_set_type !== 'equipment_category' || !nestedFrom.equipment_category.index) {\n    console.warn('ChoiceOption.choice.from is not a valid EquipmentCategoryOptionSet:', nestedFrom)\n    return null\n  }\n\n  const equipmentCategory = await EquipmentCategoryModel.findOne({\n    index: nestedFrom.equipment_category.index\n  }).lean()\n\n  if (!equipmentCategory) {\n    console.warn(`Equipment category not found: ${nestedFrom.equipment_category.index}`)\n    return null\n  }\n\n  return {\n    option_type: choiceOption.option_type,\n    choice: {\n      choose: nestedChoice.choose,\n      desc: nestedChoice.desc,\n      type: nestedChoice.type,\n      from: {\n        option_set_type: nestedFrom.option_set_type,\n        equipment_category: equipmentCategory as EquipmentCategory\n      } as EquipmentCategorySet\n    }\n  } as EquipmentCategoryChoiceOption\n}\n\nasync function resolveMultipleItemsOption(\n  multipleOption: MultipleOption\n): Promise<MultipleItemsOption | null> {\n  if (multipleOption.items.length === 0) {\n    console.warn('Invalid MultipleOption data:', multipleOption)\n    return null\n  }\n\n  const resolvedItems: Array<ResolvedCountedReferenceOption | EquipmentCategoryChoiceOption> = []\n\n  for (const item of multipleOption.items) {\n    if (!item.option_type) {\n      console.warn('Invalid item within MultipleOption items array:', item)\n      continue\n    }\n\n    let resolvedItem: ResolvedCountedReferenceOption | EquipmentCategoryChoiceOption | null = null\n\n    if (item.option_type === 'counted_reference') {\n      resolvedItem = await resolveCountedReferenceOption(item as CountedReferenceOption)\n    } else if (item.option_type === 'choice') {\n      resolvedItem = await resolveEquipmentCategoryChoiceOption(item as ChoiceOption)\n    } else {\n      console.warn(`Unknown option_type within MultipleOption items: ${item.option_type}`)\n    }\n\n    if (resolvedItem) {\n      resolvedItems.push(resolvedItem)\n    }\n  }\n\n  if (resolvedItems.length === 0 && multipleOption.items.length > 0) {\n    console.warn('All items within MultipleOption failed to resolve:', multipleOption)\n    return null\n  }\n\n  return {\n    option_type: multipleOption.option_type,\n    items: resolvedItems\n  } as MultipleItemsOption\n}\n"
  },
  {
    "path": "src/graphql/2024/common/choiceTypes.ts",
    "content": "import { Field, Int, ObjectType } from 'type-graphql'\n\nimport { AbilityScore2024 } from '@/models/2024/abilityScore'\nimport { Proficiency2024 } from '@/models/2024/proficiency'\n\n// --- Score Prerequisite Choice Types (for Feat2024.prerequisite_options) ---\n\n@ObjectType({ description: 'A score prerequisite option within a feat prerequisite choice.' })\nexport class ScorePrerequisiteOption2024 {\n  @Field(() => String, { description: 'The type of this option.' })\n  option_type!: string\n\n  @Field(() => AbilityScore2024, { description: 'The ability score required.' })\n  ability_score!: AbilityScore2024\n\n  @Field(() => Int, { description: 'The minimum score required.' })\n  minimum_score!: number\n}\n\n@ObjectType({ description: 'The set of score prerequisite options for a feat.' })\nexport class ScorePrerequisiteOptionSet2024 {\n  @Field(() => String, { description: 'The type of the option set.' })\n  option_set_type!: string\n\n  @Field(() => [ScorePrerequisiteOption2024], {\n    description: 'The available prerequisite options.'\n  })\n  options!: ScorePrerequisiteOption2024[]\n}\n\n@ObjectType({ description: 'A prerequisite choice for a 2024 feat (ability score requirements).' })\nexport class ScorePrerequisiteChoice2024 {\n  @Field(() => String, { nullable: true, description: 'Description of the prerequisite choice.' })\n  desc?: string\n\n  @Field(() => Int, { description: 'Number of options to choose.' })\n  choose!: number\n\n  @Field(() => String, { description: 'The type of choice.' })\n  type!: string\n\n  @Field(() => ScorePrerequisiteOptionSet2024, { description: 'The set of options.' })\n  from!: ScorePrerequisiteOptionSet2024\n}\n\n// --- Proficiency Choice Types (for Background2024.proficiency_choices) ---\n\n@ObjectType({ description: 'A reference to a 2024 proficiency within a choice option set.' })\nexport class Proficiency2024ChoiceOption {\n  @Field(() => String, { description: 'The type of this option.' })\n  option_type!: string\n\n  @Field(() => Proficiency2024, { description: 'The resolved Proficiency2024 object.' })\n  item!: Proficiency2024\n}\n\n@ObjectType({ description: 'The set of proficiency options for a background choice.' })\nexport class Proficiency2024ChoiceOptionSet {\n  @Field(() => String, { description: 'The type of the option set.' })\n  option_set_type!: string\n\n  @Field(() => [Proficiency2024ChoiceOption], { description: 'The available proficiency options.' })\n  options!: Proficiency2024ChoiceOption[]\n}\n\n@ObjectType({ description: 'A proficiency choice for a 2024 background.' })\nexport class Proficiency2024Choice {\n  @Field(() => String, { nullable: true, description: 'Description of the choice.' })\n  desc?: string\n\n  @Field(() => Int, { description: 'Number of proficiencies to choose.' })\n  choose!: number\n\n  @Field(() => String, { description: 'The type of choice.' })\n  type!: string\n\n  @Field(() => Proficiency2024ChoiceOptionSet, { description: 'The set of proficiency options.' })\n  from!: Proficiency2024ChoiceOptionSet\n}\n"
  },
  {
    "path": "src/graphql/2024/common/equipmentTypes.ts",
    "content": "import { Field, Int, ObjectType } from 'type-graphql'\n\nimport { AbilityScore2024 } from '@/models/2024/abilityScore'\nimport {\n  ArmorClass,\n  Content,\n  Equipment2024,\n  Range,\n  ThrowRange,\n  Utilize\n} from '@/models/2024/equipment'\nimport { WeaponProperty2024 } from '@/models/2024/weaponProperty'\nimport { APIReference } from '@/models/common/apiReference'\nimport { Damage } from '@/models/common/damage'\n\nimport { IEquipment } from './interfaces'\nimport { AnyEquipment } from './unions'\n\n@ObjectType({ description: 'Represents Armor equipment', implements: IEquipment })\nexport class Armor extends Equipment2024 {\n  @Field(() => ArmorClass, { description: 'Armor export Class details for this armor.' })\n  declare armor_class: ArmorClass\n\n  @Field(() => String, { description: 'Time to doff the armor.' })\n  declare doff_time: string\n\n  @Field(() => String, { description: 'Time to don the armor.' })\n  declare don_time: string\n\n  @Field(() => Int, {\n    nullable: true,\n    description: 'Minimum Strength score required to use this armor effectively.'\n  })\n  declare str_minimum: number\n\n  @Field(() => Boolean, {\n    nullable: true,\n    description: 'Whether wearing the armor imposes disadvantage on Stealth checks.'\n  })\n  declare stealth_disadvantage: boolean\n}\n\n@ObjectType({ description: 'Represents Weapon equipment', implements: IEquipment })\nexport class Weapon extends Equipment2024 {\n  @Field(() => Damage, { nullable: true, description: 'Primary damage dealt by the weapon.' })\n  declare damage?: Damage\n\n  @Field(() => Damage, {\n    nullable: true,\n    description: 'Damage dealt when using the weapon with two hands.'\n  })\n  declare two_handed_damage?: Damage\n\n  @Field(() => Range, { nullable: true, description: 'Weapon range details.' })\n  declare range?: Range\n\n  @Field(() => ThrowRange, { nullable: true, description: 'Range when the weapon is thrown.' })\n  declare throw_range?: ThrowRange\n\n  @Field(() => [WeaponProperty2024], { nullable: true, description: 'Properties of the weapon.' })\n  declare properties?: APIReference[] // Resolved externally\n}\n\n@ObjectType({ description: 'Represents Gear equipment (general purpose)', implements: IEquipment })\nexport class AdventuringGear extends Equipment2024 {}\n\n@ObjectType({\n  description: \"Represents Gear that contains other items (e.g., Explorer's Pack)\",\n  implements: IEquipment\n})\nexport class Pack extends AdventuringGear {\n  @Field(() => [Content], { nullable: true, description: 'Items contained within the pack.' })\n  declare contents?: Content[]\n}\n\n@ObjectType({ description: 'Represents Ammunition equipment', implements: IEquipment })\nexport class Ammunition extends AdventuringGear {\n  @Field(() => Int, { description: 'Quantity of ammunition in the bundle.' })\n  declare quantity: number\n\n  @Field(() => Equipment2024, { nullable: true, description: 'Storage of the ammunition.' })\n  declare storage?: APIReference\n}\n\n@ObjectType({ description: 'Represents Tool equipment', implements: IEquipment })\nexport class Tool extends Equipment2024 {\n  @Field(() => AbilityScore2024, {\n    nullable: true,\n    description: 'Ability score required to use the tool.'\n  })\n  declare ability?: APIReference\n\n  @Field(() => [AnyEquipment], {\n    nullable: true,\n    description: 'Equipment that can be crafted with the tool.'\n  })\n  declare craft?: APIReference[]\n\n  @Field(() => [Utilize], { nullable: true, description: 'How to utilize the tool.' })\n  declare utilize?: Utilize[]\n}\n"
  },
  {
    "path": "src/graphql/2024/common/interfaces.ts",
    "content": "import { Field, Float, InterfaceType } from 'type-graphql'\n\nimport { Cost } from '@/models/2024/equipment'\n\n@InterfaceType({\n  description: 'Common fields shared by all types of equipment and magic items.'\n})\nexport abstract class IEquipment {\n  @Field(() => String, { description: 'The unique identifier for this equipment.' })\n  index!: string\n\n  @Field(() => String, { description: 'The name of the equipment.' })\n  name!: string\n\n  @Field(() => Cost, { description: 'Cost of the equipment in coinage.' })\n  cost!: Cost\n\n  @Field(() => Float, { nullable: true, description: 'Weight of the equipment in pounds.' })\n  weight?: number\n\n  @Field(() => [String], { nullable: true, description: 'Description of the equipment.' })\n  description?: string[]\n}\n"
  },
  {
    "path": "src/graphql/2024/common/resolver.ts",
    "content": "import { FieldResolver, Resolver, Root } from 'type-graphql'\n\nimport { resolveSingleReference } from '@/graphql/utils/resolvers'\nimport AbilityScoreModel, { AbilityScore2024 } from '@/models/2024/abilityScore'\nimport { DifficultyClass } from '@/models/common/difficultyClass'\n\n@Resolver(() => DifficultyClass)\nexport class DifficultyClassResolver {\n  @FieldResolver(() => AbilityScore2024, { nullable: true })\n  async dc_type(@Root() dc: DifficultyClass): Promise<AbilityScore2024 | null> {\n    return resolveSingleReference(dc.dc_type, AbilityScoreModel)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/common/unions.ts",
    "content": "import { createUnionType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\n\nimport { Ammunition, AdventuringGear, Armor, Pack, Weapon, Tool } from './equipmentTypes'\n\nfunction resolveEquipmentType(\n  value: any\n):\n  | typeof Weapon\n  | typeof AdventuringGear\n  | typeof Ammunition\n  | typeof Armor\n  | typeof Pack\n  | typeof Tool\n  | null {\n  if (\n    value.equipment_categories?.some((category: APIReference) => category.index === 'weapons') ===\n    true\n  ) {\n    return Weapon\n  }\n  if (\n    value.equipment_categories?.some(\n      (category: APIReference) => category.index === 'ammunition'\n    ) === true\n  ) {\n    return Ammunition\n  }\n  if (\n    value.equipment_categories?.some((category: APIReference) => category.index === 'armor') ===\n    true\n  ) {\n    return Armor\n  }\n  if (\n    value.equipment_categories?.some(\n      (category: APIReference) => category.index === 'equipment-packs'\n    ) === true\n  ) {\n    return Pack\n  }\n  if (\n    value.equipment_categories?.some(\n      (category: APIReference) => category.index === 'adventuring-gear'\n    ) === true\n  ) {\n    return AdventuringGear\n  }\n  if (\n    value.equipment_categories?.some((category: APIReference) => category.index === 'tools') ===\n    true\n  ) {\n    return Tool\n  }\n  return null\n}\n\nexport const AnyEquipment = createUnionType({\n  name: 'AnyEquipment',\n  types: () => {\n    return [Weapon, AdventuringGear, Ammunition, Armor, Pack, Tool] as const\n  },\n  resolveType: (value) => {\n    const equipmentType = resolveEquipmentType(value)\n    if (equipmentType) {\n      return equipmentType\n    }\n\n    console.warn('Could not resolve type for AnyEquipment:', value)\n    return AdventuringGear\n  }\n})\n"
  },
  {
    "path": "src/graphql/2024/resolvers/abilityScore/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum AbilityScoreOrderField {\n  NAME = 'name',\n  FULL_NAME = 'full_name'\n}\n\nexport const ABILITY_SCORE_SORT_FIELD_MAP: Record<AbilityScoreOrderField, string> = {\n  [AbilityScoreOrderField.NAME]: 'name',\n  [AbilityScoreOrderField.FULL_NAME]: 'full_name'\n}\n\nregisterEnumType(AbilityScoreOrderField, {\n  name: 'AbilityScoreOrderField',\n  description: 'Fields to sort Ability Scores by'\n})\n\n@InputType()\nexport class AbilityScoreOrder implements BaseOrderInterface<AbilityScoreOrderField> {\n  @Field(() => AbilityScoreOrderField)\n  by!: AbilityScoreOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => AbilityScoreOrder, { nullable: true })\n  then_by?: AbilityScoreOrder\n}\n\nexport const AbilityScoreOrderSchema: z.ZodType<AbilityScoreOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(AbilityScoreOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: AbilityScoreOrderSchema.optional()\n  })\n)\n\nexport const AbilityScoreArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  full_name: z.string().optional(),\n  order: AbilityScoreOrderSchema.optional()\n})\n\nexport const AbilityScoreIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class AbilityScoreArgs extends BaseFilterArgs {\n  @Field(() => String, {\n    nullable: true,\n    description: 'Filter by ability score full name (case-insensitive, partial match)'\n  })\n  full_name?: string\n\n  @Field(() => AbilityScoreOrder, {\n    nullable: true,\n    description: 'Specify sorting order for ability scores.'\n  })\n  order?: AbilityScoreOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/abilityScore/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { resolveMultipleReferences } from '@/graphql/utils/resolvers'\nimport AbilityScoreModel, { AbilityScore2024 } from '@/models/2024/abilityScore'\nimport SkillModel, { Skill2024 } from '@/models/2024/skill'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  ABILITY_SCORE_SORT_FIELD_MAP,\n  AbilityScoreArgs,\n  AbilityScoreArgsSchema,\n  AbilityScoreIndexArgsSchema,\n  AbilityScoreOrderField\n} from './args'\n\n@Resolver(AbilityScore2024)\nexport class AbilityScoreResolver {\n  @Query(() => [AbilityScore2024], {\n    description: 'Gets all ability scores, optionally filtered by name and sorted.'\n  })\n  async abilityScores(\n    @Args(() => AbilityScoreArgs) args: AbilityScoreArgs\n  ): Promise<AbilityScore2024[]> {\n    const validatedArgs = AbilityScoreArgsSchema.parse(args)\n\n    const query = AbilityScoreModel.find()\n    const filters: Record<string, any>[] = []\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      filters.push({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    if (validatedArgs.full_name != null && validatedArgs.full_name !== '') {\n      filters.push({\n        full_name: { $regex: new RegExp(escapeRegExp(validatedArgs.full_name), 'i') }\n      })\n    }\n\n    if (filters.length > 0) {\n      query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<AbilityScoreOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: ABILITY_SCORE_SORT_FIELD_MAP,\n      defaultSortField: AbilityScoreOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => AbilityScore2024, {\n    nullable: true,\n    description: 'Gets a single ability score by index.'\n  })\n  async abilityScore(\n    @Arg('index', () => String) indexInput: string\n  ): Promise<AbilityScore2024 | null> {\n    const { index } = AbilityScoreIndexArgsSchema.parse({ index: indexInput })\n    return AbilityScoreModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => [Skill2024])\n  async skills(@Root() abilityScore: AbilityScore2024): Promise<Skill2024[]> {\n    return resolveMultipleReferences(abilityScore.skills, SkillModel)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/alignment/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum AlignmentOrderField {\n  NAME = 'name'\n}\n\nexport const ALIGNMENT_SORT_FIELD_MAP: Record<AlignmentOrderField, string> = {\n  [AlignmentOrderField.NAME]: 'name'\n}\n\nregisterEnumType(AlignmentOrderField, {\n  name: 'AlignmentOrderField',\n  description: 'Fields to sort Alignments by'\n})\n\n@InputType()\nexport class AlignmentOrder implements BaseOrderInterface<AlignmentOrderField> {\n  @Field(() => AlignmentOrderField)\n  by!: AlignmentOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => AlignmentOrder, { nullable: true })\n  then_by?: AlignmentOrder\n}\n\nexport const AlignmentOrderSchema: z.ZodType<AlignmentOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(AlignmentOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: AlignmentOrderSchema.optional()\n  })\n)\n\nexport const AlignmentArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: AlignmentOrderSchema.optional()\n})\n\nexport const AlignmentIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class AlignmentArgs extends BaseFilterArgs {\n  @Field(() => AlignmentOrder, {\n    nullable: true,\n    description: 'Specify sorting order for alignments.'\n  })\n  order?: AlignmentOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/alignment/resolver.ts",
    "content": "import { Arg, Args, Query, Resolver } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport AlignmentModel, { Alignment2024 } from '@/models/2024/alignment'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  ALIGNMENT_SORT_FIELD_MAP,\n  AlignmentArgs,\n  AlignmentArgsSchema,\n  AlignmentIndexArgsSchema,\n  AlignmentOrderField\n} from './args'\n\n@Resolver(Alignment2024)\nexport class AlignmentResolver {\n  @Query(() => [Alignment2024], {\n    description: 'Gets all alignments, optionally filtered by name and sorted.'\n  })\n  async alignments(@Args(() => AlignmentArgs) args: AlignmentArgs): Promise<Alignment2024[]> {\n    const validatedArgs = AlignmentArgsSchema.parse(args)\n\n    const query = AlignmentModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<AlignmentOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: ALIGNMENT_SORT_FIELD_MAP,\n      defaultSortField: AlignmentOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Alignment2024, {\n    nullable: true,\n    description: 'Gets a single alignment by index.'\n  })\n  async alignment(@Arg('index', () => String) indexInput: string): Promise<Alignment2024 | null> {\n    const { index } = AlignmentIndexArgsSchema.parse({ index: indexInput })\n    return AlignmentModel.findOne({ index }).lean()\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/background/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum BackgroundOrderField {\n  NAME = 'name'\n}\n\nexport const BACKGROUND_SORT_FIELD_MAP: Record<BackgroundOrderField, string> = {\n  [BackgroundOrderField.NAME]: 'name'\n}\n\nregisterEnumType(BackgroundOrderField, {\n  name: 'BackgroundOrderField',\n  description: 'Fields to sort Backgrounds by'\n})\n\n@InputType()\nexport class BackgroundOrder implements BaseOrderInterface<BackgroundOrderField> {\n  @Field(() => BackgroundOrderField)\n  by!: BackgroundOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => BackgroundOrder, { nullable: true })\n  then_by?: BackgroundOrder\n}\n\nexport const BackgroundOrderSchema: z.ZodType<BackgroundOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(BackgroundOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: BackgroundOrderSchema.optional()\n  })\n)\n\nexport const BackgroundArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: BackgroundOrderSchema.optional()\n})\n\nexport const BackgroundIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class BackgroundArgs extends BaseFilterArgs {\n  @Field(() => BackgroundOrder, {\n    nullable: true,\n    description: 'Specify sorting order for backgrounds.'\n  })\n  order?: BackgroundOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/background/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { Proficiency2024Choice } from '@/graphql/2024/common/choiceTypes'\nimport { BackgroundEquipmentChoice2024 } from '@/graphql/2024/types/backgroundEquipment'\nimport { resolveBackgroundEquipmentChoices } from '@/graphql/2024/utils/backgroundEquipmentResolver'\nimport { resolveProficiency2024ChoiceArray } from '@/graphql/2024/utils/choiceResolvers'\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { resolveMultipleReferences, resolveSingleReference } from '@/graphql/utils/resolvers'\nimport AbilityScoreModel, { AbilityScore2024 } from '@/models/2024/abilityScore'\nimport BackgroundModel, { Background2024, BackgroundFeatReference } from '@/models/2024/background'\nimport FeatModel, { Feat2024 } from '@/models/2024/feat'\nimport ProficiencyModel, { Proficiency2024 } from '@/models/2024/proficiency'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  BACKGROUND_SORT_FIELD_MAP,\n  BackgroundArgs,\n  BackgroundArgsSchema,\n  BackgroundIndexArgsSchema,\n  BackgroundOrderField\n} from './args'\n\n@Resolver(Background2024)\nexport class BackgroundResolver {\n  @Query(() => [Background2024], {\n    description: 'Gets all backgrounds, optionally filtered by name.'\n  })\n  async backgrounds(@Args(() => BackgroundArgs) args: BackgroundArgs): Promise<Background2024[]> {\n    const validatedArgs = BackgroundArgsSchema.parse(args)\n\n    const query = BackgroundModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<BackgroundOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: BACKGROUND_SORT_FIELD_MAP,\n      defaultSortField: BackgroundOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip !== undefined) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit !== undefined) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Background2024, { nullable: true, description: 'Gets a single background by index.' })\n  async background(\n    @Arg('index', () => String) indexInput: string\n  ): Promise<Background2024 | null> {\n    const { index } = BackgroundIndexArgsSchema.parse({ index: indexInput })\n    return BackgroundModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => [AbilityScore2024])\n  async ability_scores(@Root() background: Background2024): Promise<AbilityScore2024[]> {\n    return resolveMultipleReferences(background.ability_scores, AbilityScoreModel)\n  }\n\n  @FieldResolver(() => Feat2024)\n  async feat(@Root() background: Background2024): Promise<Feat2024 | null> {\n    return resolveSingleReference(background.feat as BackgroundFeatReference, FeatModel)\n  }\n\n  @FieldResolver(() => [Proficiency2024])\n  async proficiencies(@Root() background: Background2024): Promise<Proficiency2024[]> {\n    return resolveMultipleReferences(background.proficiencies, ProficiencyModel)\n  }\n\n  @FieldResolver(() => [Proficiency2024Choice], { nullable: true })\n  async proficiency_choices(\n    @Root() background: Background2024\n  ): Promise<Proficiency2024Choice[]> {\n    return resolveProficiency2024ChoiceArray(background.proficiency_choices)\n  }\n\n  @FieldResolver(() => [BackgroundEquipmentChoice2024], { nullable: true })\n  async equipment_options(\n    @Root() background: Background2024\n  ): Promise<BackgroundEquipmentChoice2024[]> {\n    return resolveBackgroundEquipmentChoices(background.equipment_options)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/condition/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum ConditionOrderField {\n  NAME = 'name'\n}\n\nexport const CONDITION_SORT_FIELD_MAP: Record<ConditionOrderField, string> = {\n  [ConditionOrderField.NAME]: 'name'\n}\n\nregisterEnumType(ConditionOrderField, {\n  name: 'ConditionOrderField',\n  description: 'Fields to sort Conditions by'\n})\n\n@InputType()\nexport class ConditionOrder implements BaseOrderInterface<ConditionOrderField> {\n  @Field(() => ConditionOrderField)\n  by!: ConditionOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => ConditionOrder, { nullable: true })\n  then_by?: ConditionOrder\n}\n\nexport const ConditionOrderSchema: z.ZodType<ConditionOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(ConditionOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: ConditionOrderSchema.optional()\n  })\n)\n\nexport const ConditionArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: ConditionOrderSchema.optional()\n})\n\nexport const ConditionIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class ConditionArgs extends BaseFilterArgs {\n  @Field(() => ConditionOrder, {\n    nullable: true,\n    description: 'Specify sorting order for conditions.'\n  })\n  order?: ConditionOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/condition/resolver.ts",
    "content": "import { Arg, Args, Query, Resolver } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport ConditionModel, { Condition2024 } from '@/models/2024/condition'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  CONDITION_SORT_FIELD_MAP,\n  ConditionArgs,\n  ConditionArgsSchema,\n  ConditionIndexArgsSchema,\n  ConditionOrderField\n} from './args'\n\n@Resolver(Condition2024)\nexport class ConditionResolver {\n  @Query(() => [Condition2024], {\n    description: 'Gets all conditions, optionally filtered by name and sorted by name.'\n  })\n  async conditions(@Args(() => ConditionArgs) args: ConditionArgs): Promise<Condition2024[]> {\n    const validatedArgs = ConditionArgsSchema.parse(args)\n    const query = ConditionModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<ConditionOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: CONDITION_SORT_FIELD_MAP,\n      defaultSortField: ConditionOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Condition2024, {\n    nullable: true,\n    description: 'Gets a single condition by index.'\n  })\n  async condition(@Arg('index', () => String) indexInput: string): Promise<Condition2024 | null> {\n    const { index } = ConditionIndexArgsSchema.parse({ index: indexInput })\n    return ConditionModel.findOne({ index }).lean()\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/damageType/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum DamageTypeOrderField {\n  NAME = 'name'\n}\n\nexport const DAMAGE_TYPE_SORT_FIELD_MAP: Record<DamageTypeOrderField, string> = {\n  [DamageTypeOrderField.NAME]: 'name'\n}\n\nregisterEnumType(DamageTypeOrderField, {\n  name: 'DamageTypeOrderField',\n  description: 'Fields to sort Damage Types by'\n})\n\n@InputType()\nexport class DamageTypeOrder implements BaseOrderInterface<DamageTypeOrderField> {\n  @Field(() => DamageTypeOrderField)\n  by!: DamageTypeOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => DamageTypeOrder, { nullable: true })\n  then_by?: DamageTypeOrder\n}\n\nexport const DamageTypeOrderSchema: z.ZodType<DamageTypeOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(DamageTypeOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: DamageTypeOrderSchema.optional()\n  })\n)\n\nexport const DamageTypeArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: DamageTypeOrderSchema.optional()\n})\n\nexport const DamageTypeIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class DamageTypeArgs extends BaseFilterArgs {\n  @Field(() => DamageTypeOrder, {\n    nullable: true,\n    description: 'Specify sorting order for damage types.'\n  })\n  order?: DamageTypeOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/damageType/resolver.ts",
    "content": "import { Arg, Args, Query, Resolver } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport DamageTypeModel, { DamageType2024 } from '@/models/2024/damageType'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  DAMAGE_TYPE_SORT_FIELD_MAP,\n  DamageTypeArgs,\n  DamageTypeArgsSchema,\n  DamageTypeIndexArgsSchema,\n  DamageTypeOrderField\n} from './args'\n\n@Resolver(DamageType2024)\nexport class DamageTypeResolver {\n  @Query(() => [DamageType2024], {\n    description: 'Gets all damage types, optionally filtered by name and sorted by name.'\n  })\n  async damageTypes(@Args(() => DamageTypeArgs) args: DamageTypeArgs): Promise<DamageType2024[]> {\n    const validatedArgs = DamageTypeArgsSchema.parse(args)\n    const query = DamageTypeModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<DamageTypeOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: DAMAGE_TYPE_SORT_FIELD_MAP,\n      defaultSortField: DamageTypeOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => DamageType2024, {\n    nullable: true,\n    description: 'Gets a single damage type by index.'\n  })\n  async damageType(@Arg('index', () => String) indexInput: string): Promise<DamageType2024 | null> {\n    const { index } = DamageTypeIndexArgsSchema.parse({ index: indexInput })\n    return DamageTypeModel.findOne({ index }).lean()\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/equipment/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum EquipmentOrderField {\n  NAME = 'name',\n  WEIGHT = 'weight',\n  COST_QUANTITY = 'cost_quantity'\n}\n\nexport const EQUIPMENT_SORT_FIELD_MAP: Record<EquipmentOrderField, string> = {\n  [EquipmentOrderField.NAME]: 'name',\n  [EquipmentOrderField.WEIGHT]: 'weight',\n  [EquipmentOrderField.COST_QUANTITY]: 'cost.quantity'\n}\n\nregisterEnumType(EquipmentOrderField, {\n  name: 'EquipmentOrderField',\n  description: 'Fields to sort Equipment by'\n})\n\n@InputType()\nexport class EquipmentOrder implements BaseOrderInterface<EquipmentOrderField> {\n  @Field(() => EquipmentOrderField)\n  by!: EquipmentOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => EquipmentOrder, { nullable: true })\n  then_by?: EquipmentOrder\n}\n\nexport const EquipmentOrderSchema: z.ZodType<EquipmentOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(EquipmentOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: EquipmentOrderSchema.optional() // Simplified\n  })\n)\n\nexport const EquipmentArgsSchema = BaseFilterArgsSchema.extend({\n  equipment_category: z.array(z.string()).optional(),\n  order: EquipmentOrderSchema.optional()\n})\n\nexport const EquipmentIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class EquipmentArgs extends BaseFilterArgs {\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by one or more equipment category indices (e.g., [\"weapon\", \"armor\"])'\n  })\n  equipment_category?: string[]\n\n  @Field(() => EquipmentOrder, {\n    nullable: true,\n    description: 'Specify sorting order for equipment.'\n  })\n  order?: EquipmentOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/equipment/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { Tool } from '@/graphql/2024/common/equipmentTypes'\nimport { AnyEquipment } from '@/graphql/2024/common/unions'\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { resolveMultipleReferences, resolveSingleReference } from '@/graphql/utils/resolvers'\nimport AbilityScoreModel, { AbilityScore2024 } from '@/models/2024/abilityScore'\nimport EquipmentModel, { Content, Equipment2024 } from '@/models/2024/equipment'\nimport WeaponPropertyModel, { WeaponProperty2024 } from '@/models/2024/weaponProperty'\nimport { APIReference } from '@/models/common/apiReference'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  EQUIPMENT_SORT_FIELD_MAP,\n  EquipmentArgs,\n  EquipmentArgsSchema,\n  EquipmentIndexArgsSchema,\n  EquipmentOrderField\n} from './args'\n\n@Resolver(Equipment2024)\nexport class EquipmentResolver {\n  @Query(() => [AnyEquipment], {\n    description: 'Gets all equipment, optionally filtered and sorted.'\n  })\n  async equipments(\n    @Args(() => EquipmentArgs) args: EquipmentArgs\n  ): Promise<Array<typeof AnyEquipment>> {\n    const validatedArgs = EquipmentArgsSchema.parse(args)\n    const query = EquipmentModel.find()\n    const filters: any[] = []\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      filters.push({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    if (validatedArgs.equipment_category && validatedArgs.equipment_category.length > 0) {\n      filters.push({ 'equipment_category.index': { $in: validatedArgs.equipment_category } })\n    }\n\n    if (filters.length > 0) {\n      query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<EquipmentOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: EQUIPMENT_SORT_FIELD_MAP,\n      defaultSortField: EquipmentOrderField.NAME\n    })\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return await query.lean()\n  }\n\n  @Query(() => AnyEquipment, {\n    nullable: true,\n    description: 'Gets a single piece of equipment by its index.'\n  })\n  async equipment(\n    @Arg('index', () => String) indexInput: string\n  ): Promise<typeof AnyEquipment | null> {\n    const { index } = EquipmentIndexArgsSchema.parse({ index: indexInput })\n    return EquipmentModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => [WeaponProperty2024], { nullable: true })\n  async properties(@Root() equipment: Equipment2024): Promise<WeaponProperty2024[] | null> {\n    if (!equipment.properties) return null\n    return resolveMultipleReferences(equipment.properties, WeaponPropertyModel)\n  }\n}\n\n@Resolver(Content)\nexport class ContentFieldResolver {\n  @FieldResolver(() => AnyEquipment, {\n    nullable: true,\n    description: 'Resolves the APIReference to the actual Equipment.'\n  })\n  async item(@Root() content: Content): Promise<typeof AnyEquipment | null> {\n    const itemRef: APIReference = content.item\n\n    if (!itemRef?.index) return null\n\n    return resolveSingleReference(itemRef, EquipmentModel)\n  }\n}\n\n@Resolver(() => Tool)\nexport class ToolResolver {\n  @FieldResolver(() => AbilityScore2024, { nullable: true })\n  async ability(@Root() tool: Tool): Promise<AbilityScore2024 | null> {\n    if (!tool.ability) return null\n    return resolveSingleReference(tool.ability, AbilityScoreModel)\n  }\n\n  @FieldResolver(() => [AnyEquipment], { nullable: true })\n  async craft(@Root() tool: Tool): Promise<Array<typeof AnyEquipment> | null> {\n    if (!tool.craft) return null\n    return resolveMultipleReferences(tool.craft, EquipmentModel)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/equipmentCategory/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum EquipmentCategoryOrderField {\n  NAME = 'name'\n}\n\nexport const EQUIPMENT_CATEGORY_SORT_FIELD_MAP: Record<EquipmentCategoryOrderField, string> = {\n  [EquipmentCategoryOrderField.NAME]: 'name'\n}\n\nregisterEnumType(EquipmentCategoryOrderField, {\n  name: 'EquipmentCategoryOrderField',\n  description: 'Fields to sort Equipment Categories by'\n})\n\n@InputType()\nexport class EquipmentCategoryOrder implements BaseOrderInterface<EquipmentCategoryOrderField> {\n  @Field(() => EquipmentCategoryOrderField)\n  by!: EquipmentCategoryOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => EquipmentCategoryOrder, { nullable: true })\n  then_by?: EquipmentCategoryOrder\n}\n\nexport const EquipmentCategoryOrderSchema: z.ZodType<EquipmentCategoryOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(EquipmentCategoryOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: EquipmentCategoryOrderSchema.optional()\n  })\n)\n\nexport const EquipmentCategoryArgsSchema = BaseFilterArgsSchema.extend({\n  order: EquipmentCategoryOrderSchema.optional()\n})\n\nexport const EquipmentCategoryIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class EquipmentCategoryArgs extends BaseFilterArgs {\n  @Field(() => EquipmentCategoryOrder, {\n    nullable: true,\n    description: 'Specify sorting order for equipment categories.'\n  })\n  order?: EquipmentCategoryOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/equipmentCategory/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { AnyEquipment } from '@/graphql/2024/common/unions'\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport EquipmentModel, { Equipment2024 } from '@/models/2024/equipment'\nimport EquipmentCategoryModel, { EquipmentCategory2024 } from '@/models/2024/equipmentCategory'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  EQUIPMENT_CATEGORY_SORT_FIELD_MAP,\n  EquipmentCategoryArgs,\n  EquipmentCategoryArgsSchema,\n  EquipmentCategoryIndexArgsSchema,\n  EquipmentCategoryOrderField\n} from './args'\n\n@Resolver(EquipmentCategory2024)\nexport class EquipmentCategoryResolver {\n  @Query(() => [EquipmentCategory2024], {\n    description: 'Gets all equipment categories, optionally filtered by name and sorted by name.'\n  })\n  async equipmentCategories(\n    @Args(() => EquipmentCategoryArgs) args: EquipmentCategoryArgs\n  ): Promise<EquipmentCategory2024[]> {\n    const validatedArgs = EquipmentCategoryArgsSchema.parse(args)\n    const query = EquipmentCategoryModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<EquipmentCategoryOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: EQUIPMENT_CATEGORY_SORT_FIELD_MAP,\n      defaultSortField: EquipmentCategoryOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => EquipmentCategory2024, {\n    nullable: true,\n    description: 'Gets a single equipment category by index.'\n  })\n  async equipmentCategory(\n    @Arg('index', () => String) indexInput: string\n  ): Promise<EquipmentCategory2024 | null> {\n    const { index } = EquipmentCategoryIndexArgsSchema.parse({ index: indexInput })\n    return EquipmentCategoryModel.findOne({ index }).lean()\n  }\n\n  // TODO: Remove when Magic Items are added\n  @FieldResolver(() => [AnyEquipment])\n  async equipment(@Root() equipmentCategory: EquipmentCategory2024): Promise<Equipment2024[]> {\n    if (equipmentCategory.equipment.length === 0) {\n      return []\n    }\n\n    const equipmentIndices = equipmentCategory.equipment.map((ref) => ref.index)\n\n    const equipments = await EquipmentModel.find({ index: { $in: equipmentIndices } }).lean()\n\n    return equipments\n  }\n\n  // TODO: Add Magic Items\n  // @FieldResolver(() => [EquipmentOrMagicItem])\n  // async equipment(\n  //   @Root() equipmentCategory: EquipmentCategory\n  // ): Promise<(Equipment | MagicItem)[]> {\n  //   if (equipmentCategory.equipment.length === 0) {\n  //     return []\n  //   }\n\n  //   const equipmentIndices = equipmentCategory.equipment.map((ref) => ref.index)\n\n  //   // Fetch both Equipment and MagicItems matching the indices\n  //   const [equipments, magicItems] = await Promise.all([\n  //     EquipmentModel.find({ index: { $in: equipmentIndices } }).lean(),\n  //     MagicItemModel.find({ index: { $in: equipmentIndices } }).lean()\n  //   ])\n\n  //   return [...equipments, ...magicItems]\n  // }\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/feat/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum FeatOrderField {\n  NAME = 'name',\n  TYPE = 'type'\n}\n\nexport const FEAT_SORT_FIELD_MAP: Record<FeatOrderField, string> = {\n  [FeatOrderField.NAME]: 'name',\n  [FeatOrderField.TYPE]: 'type'\n}\n\nregisterEnumType(FeatOrderField, {\n  name: 'FeatOrderField',\n  description: 'Fields to sort Feats by'\n})\n\n@InputType()\nexport class FeatOrder implements BaseOrderInterface<FeatOrderField> {\n  @Field(() => FeatOrderField)\n  by!: FeatOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => FeatOrder, { nullable: true })\n  then_by?: FeatOrder\n}\n\nexport const FeatOrderSchema: z.ZodType<FeatOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(FeatOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: FeatOrderSchema.optional()\n  })\n)\n\nexport const FeatArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  type: z.array(z.string()).optional(),\n  order: FeatOrderSchema.optional()\n})\n\nexport const FeatIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class FeatArgs extends BaseFilterArgs {\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by feat type (e.g., [\"origin\", \"general\"])'\n  })\n  type?: string[]\n\n  @Field(() => FeatOrder, { nullable: true, description: 'Specify sorting order for feats.' })\n  order?: FeatOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/feat/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { ScorePrerequisiteChoice2024 } from '@/graphql/2024/common/choiceTypes'\nimport { resolveScorePrerequisiteChoice } from '@/graphql/2024/utils/choiceResolvers'\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport FeatModel, { Feat2024 } from '@/models/2024/feat'\nimport { escapeRegExp } from '@/util'\n\nimport { FEAT_SORT_FIELD_MAP, FeatArgs, FeatArgsSchema, FeatIndexArgsSchema, FeatOrderField } from './args'\n\n@Resolver(Feat2024)\nexport class FeatResolver {\n  @Query(() => [Feat2024], {\n    description: 'Gets all feats, optionally filtered by name and type.'\n  })\n  async feats(@Args(() => FeatArgs) args: FeatArgs): Promise<Feat2024[]> {\n    const validatedArgs = FeatArgsSchema.parse(args)\n\n    const query = FeatModel.find()\n    const filters: any[] = []\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      filters.push({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    if (validatedArgs.type && validatedArgs.type.length > 0) {\n      filters.push({ type: { $in: validatedArgs.type } })\n    }\n\n    if (filters.length > 0) {\n      query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<FeatOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: FEAT_SORT_FIELD_MAP,\n      defaultSortField: FeatOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip !== undefined) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit !== undefined) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Feat2024, { nullable: true, description: 'Gets a single feat by index.' })\n  async feat(@Arg('index', () => String) indexInput: string): Promise<Feat2024 | null> {\n    const { index } = FeatIndexArgsSchema.parse({ index: indexInput })\n    return FeatModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => ScorePrerequisiteChoice2024, { nullable: true })\n  async prerequisite_options(@Root() feat: Feat2024): Promise<ScorePrerequisiteChoice2024 | null> {\n    return resolveScorePrerequisiteChoice(feat.prerequisite_options)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/index.ts",
    "content": "import { DifficultyClassResolver } from '../common/resolver'\nimport { AbilityScoreResolver } from './abilityScore/resolver'\nimport { AlignmentResolver } from './alignment/resolver'\nimport { BackgroundResolver } from './background/resolver'\nimport { ConditionResolver } from './condition/resolver'\nimport { DamageTypeResolver } from './damageType/resolver'\nimport { ContentFieldResolver, EquipmentResolver, ToolResolver } from './equipment/resolver'\nimport { EquipmentCategoryResolver } from './equipmentCategory/resolver'\nimport { FeatResolver } from './feat/resolver'\nimport { LanguageResolver } from './language/resolver'\nimport { MagicItemResolver } from './magicItem/resolver'\nimport { MagicSchoolResolver } from './magicSchool/resolver'\nimport { ProficiencyResolver } from './proficiency/resolver'\nimport { SkillResolver } from './skill/resolver'\nimport { SpeciesResolver } from './species/resolver'\nimport { SubclassResolver } from './subclass/resolver'\nimport { SubspeciesResolver } from './subspecies/resolver'\nimport { TraitResolver } from './trait/resolver'\nimport { WeaponMasteryPropertyResolver } from './weaponMasteryProperty/resolver'\nimport { WeaponPropertyResolver } from './weaponProperty/resolver'\n\nconst collectionResolvers = [\n  AbilityScoreResolver,\n  AlignmentResolver,\n  BackgroundResolver,\n  ConditionResolver,\n  DamageTypeResolver,\n  EquipmentResolver,\n  EquipmentCategoryResolver,\n  FeatResolver,\n  LanguageResolver,\n  MagicItemResolver,\n  MagicSchoolResolver,\n  ProficiencyResolver,\n  SkillResolver,\n  SpeciesResolver,\n  SubclassResolver,\n  SubspeciesResolver,\n  TraitResolver,\n  WeaponMasteryPropertyResolver,\n  WeaponPropertyResolver\n] as const\n\nconst fieldResolvers = [\n  // Equipment\n  ContentFieldResolver,\n  ToolResolver,\n  DifficultyClassResolver\n] as const\n\nexport const resolvers = [...collectionResolvers, ...fieldResolvers] as const\n"
  },
  {
    "path": "src/graphql/2024/resolvers/language/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum LanguageOrderField {\n  NAME = 'name',\n  TYPE = 'type',\n  SCRIPT = 'script'\n}\n\nexport const LANGUAGE_SORT_FIELD_MAP: Record<LanguageOrderField, string> = {\n  [LanguageOrderField.NAME]: 'name',\n  [LanguageOrderField.TYPE]: 'type',\n  [LanguageOrderField.SCRIPT]: 'script'\n}\n\nregisterEnumType(LanguageOrderField, {\n  name: 'LanguageOrderField',\n  description: 'Fields to sort Languages by'\n})\n\n@InputType()\nexport class LanguageOrder implements BaseOrderInterface<LanguageOrderField> {\n  @Field(() => LanguageOrderField)\n  by!: LanguageOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => LanguageOrder, { nullable: true })\n  then_by?: LanguageOrder\n}\n\nexport const LanguageOrderSchema: z.ZodType<LanguageOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(LanguageOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: LanguageOrderSchema.optional()\n  })\n)\n\nexport const LanguageArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  type: z.string().optional(),\n  script: z.array(z.string()).optional(),\n  order: LanguageOrderSchema.optional()\n})\n\nexport const LanguageIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class LanguageArgs extends BaseFilterArgs {\n  @Field(() => String, {\n    nullable: true,\n    description:\n      'Filter by language type (e.g., Standard, Exotic) - case-insensitive exact match after normalization'\n  })\n  type?: string\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by one or more language scripts (e.g., [\"Common\", \"Elvish\"])'\n  })\n  script?: string[]\n\n  @Field(() => LanguageOrder, {\n    nullable: true,\n    description: 'Specify sorting order for languages.'\n  })\n  order?: LanguageOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/language/resolver.ts",
    "content": "import { Arg, Args, Query, Resolver } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport LanguageModel, { Language2024 } from '@/models/2024/language'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  LANGUAGE_SORT_FIELD_MAP,\n  LanguageArgs,\n  LanguageArgsSchema,\n  LanguageIndexArgsSchema,\n  LanguageOrderField\n} from './args'\n\n@Resolver(Language2024)\nexport class LanguageResolver {\n  @Query(() => Language2024, {\n    nullable: true,\n    description: 'Gets a single language by its index.'\n  })\n  async language(@Arg('index', () => String) indexInput: string): Promise<Language2024 | null> {\n    const { index } = LanguageIndexArgsSchema.parse({ index: indexInput })\n    return LanguageModel.findOne({ index }).lean()\n  }\n\n  @Query(() => [Language2024], {\n    description: 'Gets all languages, optionally filtered and sorted.'\n  })\n  async languages(@Args(() => LanguageArgs) args: LanguageArgs): Promise<Language2024[]> {\n    const validatedArgs = LanguageArgsSchema.parse(args)\n\n    const query = LanguageModel.find()\n    const filters: any[] = []\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      filters.push({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    if (validatedArgs.type != null && validatedArgs.type !== '') {\n      filters.push({ type: { $regex: new RegExp(escapeRegExp(validatedArgs.type), 'i') } })\n    }\n\n    if (validatedArgs.script && validatedArgs.script.length > 0) {\n      filters.push({ script: { $in: validatedArgs.script } })\n    }\n\n    if (filters.length > 0) {\n      query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<LanguageOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: LANGUAGE_SORT_FIELD_MAP,\n      defaultSortField: LanguageOrderField.NAME\n    })\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip !== undefined) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit !== undefined) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/magicItem/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum MagicItemOrderField {\n  NAME = 'name'\n}\n\nexport const MAGIC_ITEM_SORT_FIELD_MAP: Record<MagicItemOrderField, string> = {\n  [MagicItemOrderField.NAME]: 'name'\n}\n\nregisterEnumType(MagicItemOrderField, {\n  name: 'MagicItemOrderField',\n  description: 'Fields to sort Magic Items by'\n})\n\n@InputType()\nexport class MagicItemOrder implements BaseOrderInterface<MagicItemOrderField> {\n  @Field(() => MagicItemOrderField)\n  by!: MagicItemOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => MagicItemOrder, { nullable: true })\n  then_by?: MagicItemOrder\n}\n\nexport const MagicItemOrderSchema: z.ZodType<MagicItemOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(MagicItemOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: MagicItemOrderSchema.optional()\n  })\n)\n\nexport const MagicItemArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  equipment_category: z.array(z.string()).optional(),\n  rarity: z.array(z.string()).optional(),\n  order: MagicItemOrderSchema.optional()\n})\n\nexport const MagicItemIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class MagicItemArgs extends BaseFilterArgs {\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by equipment category index (e.g., [\"wondrous-items\", \"armor\"])'\n  })\n  equipment_category?: string[]\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by rarity name (e.g., [\"Common\", \"Rare\"])'\n  })\n  rarity?: string[]\n\n  @Field(() => MagicItemOrder, {\n    nullable: true,\n    description: 'Specify sorting order for magic items.'\n  })\n  order?: MagicItemOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/magicItem/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { resolveSingleReference, resolveMultipleReferences } from '@/graphql/utils/resolvers'\nimport EquipmentCategoryModel, { EquipmentCategory2024 } from '@/models/2024/equipmentCategory'\nimport MagicItemModel, { MagicItem2024 } from '@/models/2024/magicItem'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  MAGIC_ITEM_SORT_FIELD_MAP,\n  MagicItemArgs,\n  MagicItemArgsSchema,\n  MagicItemIndexArgsSchema,\n  MagicItemOrderField\n} from './args'\n\n@Resolver(MagicItem2024)\nexport class MagicItemResolver {\n  @Query(() => [MagicItem2024], {\n    description: 'Gets all magic items, optionally filtered by name, equipment category, or rarity.'\n  })\n  async magicItems(@Args(() => MagicItemArgs) args: MagicItemArgs): Promise<MagicItem2024[]> {\n    const validatedArgs = MagicItemArgsSchema.parse(args)\n    const query = MagicItemModel.find()\n    const filters: any[] = []\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      filters.push({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    if (validatedArgs.equipment_category && validatedArgs.equipment_category.length > 0) {\n      filters.push({ 'equipment_category.index': { $in: validatedArgs.equipment_category } })\n    }\n\n    if (validatedArgs.rarity && validatedArgs.rarity.length > 0) {\n      filters.push({ 'rarity.name': { $in: validatedArgs.rarity } })\n    }\n\n    if (filters.length > 0) {\n      query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<MagicItemOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: MAGIC_ITEM_SORT_FIELD_MAP,\n      defaultSortField: MagicItemOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => MagicItem2024, {\n    nullable: true,\n    description: 'Gets a single magic item by index.'\n  })\n  async magicItem(@Arg('index', () => String) indexInput: string): Promise<MagicItem2024 | null> {\n    const { index } = MagicItemIndexArgsSchema.parse({ index: indexInput })\n    return MagicItemModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => EquipmentCategory2024)\n  async equipment_category(@Root() magicItem: MagicItem2024): Promise<EquipmentCategory2024 | null> {\n    return resolveSingleReference(magicItem.equipment_category, EquipmentCategoryModel)\n  }\n\n  @FieldResolver(() => [MagicItem2024])\n  async variants(@Root() magicItem: MagicItem2024): Promise<MagicItem2024[]> {\n    return resolveMultipleReferences(magicItem.variants, MagicItemModel)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/magicSchool/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum MagicSchoolOrderField {\n  NAME = 'name'\n}\n\nexport const MAGIC_SCHOOL_SORT_FIELD_MAP: Record<MagicSchoolOrderField, string> = {\n  [MagicSchoolOrderField.NAME]: 'name'\n}\n\nregisterEnumType(MagicSchoolOrderField, {\n  name: 'MagicSchoolOrderField',\n  description: 'Fields to sort Magic Schools by'\n})\n\n@InputType()\nexport class MagicSchoolOrder implements BaseOrderInterface<MagicSchoolOrderField> {\n  @Field(() => MagicSchoolOrderField)\n  by!: MagicSchoolOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => MagicSchoolOrder, { nullable: true })\n  then_by?: MagicSchoolOrder\n}\n\nexport const MagicSchoolOrderSchema: z.ZodType<MagicSchoolOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(MagicSchoolOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: MagicSchoolOrderSchema.optional()\n  })\n)\n\nexport const MagicSchoolArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: MagicSchoolOrderSchema.optional()\n})\n\nexport const MagicSchoolIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class MagicSchoolArgs extends BaseFilterArgs {\n  @Field(() => MagicSchoolOrder, {\n    nullable: true,\n    description: 'Specify sorting order for magic schools.'\n  })\n  order?: MagicSchoolOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/magicSchool/resolver.ts",
    "content": "import { Arg, Args, Query, Resolver } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport MagicSchoolModel, { MagicSchool2024 } from '@/models/2024/magicSchool'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  MAGIC_SCHOOL_SORT_FIELD_MAP,\n  MagicSchoolArgs,\n  MagicSchoolArgsSchema,\n  MagicSchoolIndexArgsSchema,\n  MagicSchoolOrderField\n} from './args'\n\n@Resolver(MagicSchool2024)\nexport class MagicSchoolResolver {\n  @Query(() => [MagicSchool2024], {\n    description: 'Gets all magic schools, optionally filtered by name and sorted by name.'\n  })\n  async magicSchools(\n    @Args(() => MagicSchoolArgs) args: MagicSchoolArgs\n  ): Promise<MagicSchool2024[]> {\n    const validatedArgs = MagicSchoolArgsSchema.parse(args)\n    const query = MagicSchoolModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<MagicSchoolOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: MAGIC_SCHOOL_SORT_FIELD_MAP,\n      defaultSortField: MagicSchoolOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => MagicSchool2024, {\n    nullable: true,\n    description: 'Gets a single magic school by index.'\n  })\n  async magicSchool(\n    @Arg('index', () => String) indexInput: string\n  ): Promise<MagicSchool2024 | null> {\n    const { index } = MagicSchoolIndexArgsSchema.parse({ index: indexInput })\n    return MagicSchoolModel.findOne({ index }).lean()\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/proficiency/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum ProficiencyOrderField {\n  NAME = 'name',\n  TYPE = 'type'\n}\n\nexport const PROFICIENCY_SORT_FIELD_MAP: Record<ProficiencyOrderField, string> = {\n  [ProficiencyOrderField.NAME]: 'name',\n  [ProficiencyOrderField.TYPE]: 'type'\n}\n\nregisterEnumType(ProficiencyOrderField, {\n  name: 'ProficiencyOrderField',\n  description: 'Fields to sort Proficiencies by'\n})\n\n@InputType()\nexport class ProficiencyOrder implements BaseOrderInterface<ProficiencyOrderField> {\n  @Field(() => ProficiencyOrderField)\n  by!: ProficiencyOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => ProficiencyOrder, { nullable: true })\n  then_by?: ProficiencyOrder\n}\n\nexport const ProficiencyOrderSchema: z.ZodType<ProficiencyOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(ProficiencyOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: ProficiencyOrderSchema.optional()\n  })\n)\n\nexport const ProficiencyArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  type: z.array(z.string()).optional(),\n  order: ProficiencyOrderSchema.optional()\n})\n\nexport const ProficiencyIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class ProficiencyArgs extends BaseFilterArgs {\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by proficiency type (e.g., [\"Skills\", \"Tools\"])'\n  })\n  type?: string[]\n\n  @Field(() => ProficiencyOrder, {\n    nullable: true,\n    description: 'Specify sorting order for proficiencies.'\n  })\n  order?: ProficiencyOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/proficiency/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { resolveMultipleReferences } from '@/graphql/utils/resolvers'\nimport BackgroundModel, { Background2024 } from '@/models/2024/background'\nimport ProficiencyModel, { Proficiency2024 } from '@/models/2024/proficiency'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  PROFICIENCY_SORT_FIELD_MAP,\n  ProficiencyArgs,\n  ProficiencyArgsSchema,\n  ProficiencyIndexArgsSchema,\n  ProficiencyOrderField\n} from './args'\n\n@Resolver(Proficiency2024)\nexport class ProficiencyResolver {\n  @Query(() => [Proficiency2024], {\n    description: 'Gets all proficiencies, optionally filtered by name and type.'\n  })\n  async proficiencies(\n    @Args(() => ProficiencyArgs) args: ProficiencyArgs\n  ): Promise<Proficiency2024[]> {\n    const validatedArgs = ProficiencyArgsSchema.parse(args)\n\n    const query = ProficiencyModel.find()\n    const filters: any[] = []\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      filters.push({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    if (validatedArgs.type && validatedArgs.type.length > 0) {\n      filters.push({ type: { $in: validatedArgs.type } })\n    }\n\n    if (filters.length > 0) {\n      query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<ProficiencyOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: PROFICIENCY_SORT_FIELD_MAP,\n      defaultSortField: ProficiencyOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip !== undefined) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit !== undefined) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Proficiency2024, {\n    nullable: true,\n    description: 'Gets a single proficiency by index.'\n  })\n  async proficiency(\n    @Arg('index', () => String) indexInput: string\n  ): Promise<Proficiency2024 | null> {\n    const { index } = ProficiencyIndexArgsSchema.parse({ index: indexInput })\n    return ProficiencyModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => [Background2024])\n  async backgrounds(@Root() proficiency: Proficiency2024): Promise<Background2024[]> {\n    return resolveMultipleReferences(proficiency.backgrounds, BackgroundModel)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/skill/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum SkillOrderField {\n  NAME = 'name',\n  ABILITY_SCORE = 'ability_score'\n}\n\nexport const SKILL_SORT_FIELD_MAP: Record<SkillOrderField, string> = {\n  [SkillOrderField.NAME]: 'name',\n  [SkillOrderField.ABILITY_SCORE]: 'ability_score.name'\n}\n\nregisterEnumType(SkillOrderField, {\n  name: 'SkillOrderField',\n  description: 'Fields to sort Skills by'\n})\n\n@InputType()\nexport class SkillOrder implements BaseOrderInterface<SkillOrderField> {\n  @Field(() => SkillOrderField)\n  by!: SkillOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => SkillOrder, { nullable: true })\n  then_by?: SkillOrder\n}\n\nexport const SkillOrderSchema: z.ZodType<SkillOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(SkillOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: SkillOrderSchema.optional()\n  })\n)\n\nexport const SkillArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  ability_score: z.array(z.string()).optional(),\n  order: SkillOrderSchema.optional()\n})\n\nexport const SkillIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class SkillArgs extends BaseFilterArgs {\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Filter by ability score index (e.g., [\"str\", \"dex\"])'\n  })\n  ability_score?: string[]\n\n  @Field(() => SkillOrder, {\n    nullable: true,\n    description: 'Specify sorting order for skills.'\n  })\n  order?: SkillOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/skill/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { resolveSingleReference } from '@/graphql/utils/resolvers'\nimport AbilityScoreModel, { AbilityScore2024 } from '@/models/2024/abilityScore'\nimport SkillModel, { Skill2024 } from '@/models/2024/skill'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  SKILL_SORT_FIELD_MAP,\n  SkillArgs,\n  SkillArgsSchema,\n  SkillIndexArgsSchema,\n  SkillOrderField\n} from './args'\n\n@Resolver(Skill2024)\nexport class SkillResolver {\n  @Query(() => [Skill2024], {\n    description: 'Gets all skills, optionally filtered by name and sorted by name.'\n  })\n  async skills(@Args(() => SkillArgs) args: SkillArgs): Promise<Skill2024[]> {\n    const validatedArgs = SkillArgsSchema.parse(args)\n\n    const query = SkillModel.find()\n    const filters: any[] = []\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      filters.push({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    if (validatedArgs.ability_score && validatedArgs.ability_score.length > 0) {\n      filters.push({ 'ability_score.index': { $in: validatedArgs.ability_score } })\n    }\n\n    if (filters.length > 0) {\n      query.where({ $and: filters })\n    }\n\n    const sortQuery = buildSortPipeline<SkillOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: SKILL_SORT_FIELD_MAP,\n      defaultSortField: SkillOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip !== undefined) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit !== undefined) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Skill2024, { nullable: true, description: 'Gets a single skill by index.' })\n  async skill(@Arg('index', () => String) indexInput: string): Promise<Skill2024 | null> {\n    const { index } = SkillIndexArgsSchema.parse({ index: indexInput })\n    return SkillModel.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => AbilityScore2024)\n  async ability_score(@Root() skill: Skill2024): Promise<AbilityScore2024 | null> {\n    return resolveSingleReference(skill.ability_score, AbilityScoreModel)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/species/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum SpeciesOrderField {\n  NAME = 'name'\n}\n\nexport const SPECIES_SORT_FIELD_MAP: Record<SpeciesOrderField, string> = {\n  [SpeciesOrderField.NAME]: 'name'\n}\n\nregisterEnumType(SpeciesOrderField, {\n  name: 'Species2024OrderField',\n  description: 'Fields to sort Species by'\n})\n\n@InputType()\nexport class SpeciesOrder implements BaseOrderInterface<SpeciesOrderField> {\n  @Field(() => SpeciesOrderField)\n  by!: SpeciesOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => SpeciesOrder, { nullable: true })\n  then_by?: SpeciesOrder\n}\n\nexport const SpeciesOrderSchema: z.ZodType<SpeciesOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(SpeciesOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: SpeciesOrderSchema.optional()\n  })\n)\n\nexport const SpeciesArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: SpeciesOrderSchema.optional()\n})\n\nexport const SpeciesIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class SpeciesArgs extends BaseFilterArgs {\n  @Field(() => SpeciesOrder, {\n    nullable: true,\n    description: 'Specify sorting order for species.'\n  })\n  order?: SpeciesOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/species/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { resolveMultipleReferences } from '@/graphql/utils/resolvers'\nimport Species2024Model, { Species2024 } from '@/models/2024/species'\nimport Subspecies2024Model, { Subspecies2024 } from '@/models/2024/subspecies'\nimport Trait2024Model, { Trait2024 } from '@/models/2024/trait'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  SPECIES_SORT_FIELD_MAP,\n  SpeciesArgs,\n  SpeciesArgsSchema,\n  SpeciesIndexArgsSchema,\n  SpeciesOrderField\n} from './args'\n\n@Resolver(Species2024)\nexport class SpeciesResolver {\n  @Query(() => [Species2024], {\n    description: 'Gets all species, optionally filtered by name and sorted by name.'\n  })\n  async species2024(@Args(() => SpeciesArgs) args: SpeciesArgs): Promise<Species2024[]> {\n    const validatedArgs = SpeciesArgsSchema.parse(args)\n    const query = Species2024Model.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<SpeciesOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: SPECIES_SORT_FIELD_MAP,\n      defaultSortField: SpeciesOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Species2024, { nullable: true, description: 'Gets a single species by index.' })\n  async species2024ByIndex(\n    @Arg('index', () => String) indexInput: string\n  ): Promise<Species2024 | null> {\n    const { index } = SpeciesIndexArgsSchema.parse({ index: indexInput })\n    return Species2024Model.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => [Subspecies2024], {\n    description: 'The subspecies available for this species.'\n  })\n  async subspecies(@Root() species: Species2024): Promise<Subspecies2024[]> {\n    return resolveMultipleReferences(species.subspecies, Subspecies2024Model)\n  }\n\n  @FieldResolver(() => [Trait2024], {\n    description: 'The traits granted by this species.'\n  })\n  async traits(@Root() species: Species2024): Promise<Trait2024[]> {\n    return resolveMultipleReferences(species.traits, Trait2024Model)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/subclass/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum SubclassOrderField {\n  NAME = 'name'\n}\n\nexport const SUBCLASS_SORT_FIELD_MAP: Record<SubclassOrderField, string> = {\n  [SubclassOrderField.NAME]: 'name'\n}\n\nregisterEnumType(SubclassOrderField, {\n  name: 'SubclassOrderField',\n  description: 'Fields to sort Subclasses by'\n})\n\n@InputType()\nexport class SubclassOrder implements BaseOrderInterface<SubclassOrderField> {\n  @Field(() => SubclassOrderField)\n  by!: SubclassOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => SubclassOrder, { nullable: true })\n  then_by?: SubclassOrder\n}\n\nexport const SubclassOrderSchema: z.ZodType<SubclassOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(SubclassOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: SubclassOrderSchema.optional()\n  })\n)\n\nexport const SubclassArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: SubclassOrderSchema.optional()\n})\n\nexport const SubclassIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class SubclassArgs extends BaseFilterArgs {\n  @Field(() => SubclassOrder, {\n    nullable: true,\n    description: 'Specify sorting order for subclasses.'\n  })\n  order?: SubclassOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/subclass/resolver.ts",
    "content": "import { Arg, Args, Query, Resolver } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport SubclassModel, { Subclass2024 } from '@/models/2024/subclass'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  SUBCLASS_SORT_FIELD_MAP,\n  SubclassArgs,\n  SubclassArgsSchema,\n  SubclassIndexArgsSchema,\n  SubclassOrderField\n} from './args'\n\n@Resolver(Subclass2024)\nexport class SubclassResolver {\n  @Query(() => [Subclass2024], {\n    description: 'Gets all subclasses, optionally filtered by name.'\n  })\n  async subclasses(@Args(() => SubclassArgs) args: SubclassArgs): Promise<Subclass2024[]> {\n    const validatedArgs = SubclassArgsSchema.parse(args)\n    const query = SubclassModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<SubclassOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: SUBCLASS_SORT_FIELD_MAP,\n      defaultSortField: SubclassOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Subclass2024, {\n    nullable: true,\n    description: 'Gets a single subclass by index.'\n  })\n  async subclass(@Arg('index', () => String) indexInput: string): Promise<Subclass2024 | null> {\n    const { index } = SubclassIndexArgsSchema.parse({ index: indexInput })\n    return SubclassModel.findOne({ index }).lean()\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/subspecies/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum SubspeciesOrderField {\n  NAME = 'name'\n}\n\nexport const SUBSPECIES_SORT_FIELD_MAP: Record<SubspeciesOrderField, string> = {\n  [SubspeciesOrderField.NAME]: 'name'\n}\n\nregisterEnumType(SubspeciesOrderField, {\n  name: 'Subspecies2024OrderField',\n  description: 'Fields to sort Subspecies by'\n})\n\n@InputType()\nexport class SubspeciesOrder implements BaseOrderInterface<SubspeciesOrderField> {\n  @Field(() => SubspeciesOrderField)\n  by!: SubspeciesOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => SubspeciesOrder, { nullable: true })\n  then_by?: SubspeciesOrder\n}\n\nexport const SubspeciesOrderSchema: z.ZodType<SubspeciesOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(SubspeciesOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: SubspeciesOrderSchema.optional()\n  })\n)\n\nexport const SubspeciesArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: SubspeciesOrderSchema.optional()\n})\n\nexport const SubspeciesIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class SubspeciesArgs extends BaseFilterArgs {\n  @Field(() => SubspeciesOrder, {\n    nullable: true,\n    description: 'Specify sorting order for subspecies.'\n  })\n  order?: SubspeciesOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/subspecies/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { resolveMultipleReferences, resolveSingleReference } from '@/graphql/utils/resolvers'\nimport DamageType2024Model, { DamageType2024 } from '@/models/2024/damageType'\nimport Species2024Model, { Species2024 } from '@/models/2024/species'\nimport Subspecies2024Model, { Subspecies2024 } from '@/models/2024/subspecies'\nimport Trait2024Model, { Trait2024 } from '@/models/2024/trait'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  SUBSPECIES_SORT_FIELD_MAP,\n  SubspeciesArgs,\n  SubspeciesArgsSchema,\n  SubspeciesIndexArgsSchema,\n  SubspeciesOrderField\n} from './args'\n\n@Resolver(Subspecies2024)\nexport class SubspeciesResolver {\n  @Query(() => [Subspecies2024], {\n    description: 'Gets all subspecies, optionally filtered by name and sorted by name.'\n  })\n  async subspecies2024(\n    @Args(() => SubspeciesArgs) args: SubspeciesArgs\n  ): Promise<Subspecies2024[]> {\n    const validatedArgs = SubspeciesArgsSchema.parse(args)\n    const query = Subspecies2024Model.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<SubspeciesOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: SUBSPECIES_SORT_FIELD_MAP,\n      defaultSortField: SubspeciesOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Subspecies2024, {\n    nullable: true,\n    description: 'Gets a single subspecies by index.'\n  })\n  async subspecies2024ByIndex(\n    @Arg('index', () => String) indexInput: string\n  ): Promise<Subspecies2024 | null> {\n    const { index } = SubspeciesIndexArgsSchema.parse({ index: indexInput })\n    return Subspecies2024Model.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => Species2024, {\n    description: 'The parent species of this subspecies.'\n  })\n  async species(@Root() subspecies: Subspecies2024): Promise<Species2024 | null> {\n    return resolveSingleReference(subspecies.species, Species2024Model)\n  }\n\n  @FieldResolver(() => [Trait2024], {\n    description: 'The traits associated with this subspecies.'\n  })\n  async traits(@Root() subspecies: Subspecies2024): Promise<Trait2024[]> {\n    return resolveMultipleReferences(subspecies.traits, Trait2024Model)\n  }\n\n  @FieldResolver(() => DamageType2024, {\n    nullable: true,\n    description: 'The damage type associated with this subspecies (Dragonborn only).'\n  })\n  async damage_type(@Root() subspecies: Subspecies2024): Promise<DamageType2024 | null> {\n    return resolveSingleReference(subspecies.damage_type, DamageType2024Model)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/trait/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum TraitOrderField {\n  NAME = 'name'\n}\n\nexport const TRAIT_SORT_FIELD_MAP: Record<TraitOrderField, string> = {\n  [TraitOrderField.NAME]: 'name'\n}\n\nregisterEnumType(TraitOrderField, {\n  name: 'Trait2024OrderField',\n  description: 'Fields to sort Traits by'\n})\n\n@InputType()\nexport class TraitOrder implements BaseOrderInterface<TraitOrderField> {\n  @Field(() => TraitOrderField)\n  by!: TraitOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => TraitOrder, { nullable: true })\n  then_by?: TraitOrder\n}\n\nexport const TraitOrderSchema: z.ZodType<TraitOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(TraitOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: TraitOrderSchema.optional()\n  })\n)\n\nexport const TraitArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: TraitOrderSchema.optional()\n})\n\nexport const TraitIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class TraitArgs extends BaseFilterArgs {\n  @Field(() => TraitOrder, {\n    nullable: true,\n    description: 'Specify sorting order for traits.'\n  })\n  order?: TraitOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/trait/resolver.ts",
    "content": "import { Arg, Args, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport { resolveMultipleReferences } from '@/graphql/utils/resolvers'\nimport Species2024Model, { Species2024 } from '@/models/2024/species'\nimport Subspecies2024Model, { Subspecies2024 } from '@/models/2024/subspecies'\nimport Trait2024Model, { Trait2024 } from '@/models/2024/trait'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  TRAIT_SORT_FIELD_MAP,\n  TraitArgs,\n  TraitArgsSchema,\n  TraitIndexArgsSchema,\n  TraitOrderField\n} from './args'\n\n@Resolver(Trait2024)\nexport class TraitResolver {\n  @Query(() => [Trait2024], {\n    description: 'Gets all traits, optionally filtered by name and sorted by name.'\n  })\n  async traits2024(@Args(() => TraitArgs) args: TraitArgs): Promise<Trait2024[]> {\n    const validatedArgs = TraitArgsSchema.parse(args)\n    const query = Trait2024Model.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<TraitOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: TRAIT_SORT_FIELD_MAP,\n      defaultSortField: TraitOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => Trait2024, { nullable: true, description: 'Gets a single trait by index.' })\n  async trait2024(@Arg('index', () => String) indexInput: string): Promise<Trait2024 | null> {\n    const { index } = TraitIndexArgsSchema.parse({ index: indexInput })\n    return Trait2024Model.findOne({ index }).lean()\n  }\n\n  @FieldResolver(() => [Species2024], {\n    description: 'The species that grant this trait.'\n  })\n  async species(@Root() trait: Trait2024): Promise<Species2024[]> {\n    return resolveMultipleReferences(trait.species, Species2024Model)\n  }\n\n  @FieldResolver(() => [Subspecies2024], {\n    description: 'The subspecies that grant this trait.'\n  })\n  async subspecies(@Root() trait: Trait2024): Promise<Subspecies2024[]> {\n    return resolveMultipleReferences(trait.subspecies, Subspecies2024Model)\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/weaponMasteryProperty/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum WeaponMasteryPropertyOrderField {\n  NAME = 'name'\n}\n\nexport const WEAPON_MASTERY_PROPERTY_SORT_FIELD_MAP: Record<\n  WeaponMasteryPropertyOrderField,\n  string\n> = {\n  [WeaponMasteryPropertyOrderField.NAME]: 'name'\n}\n\nregisterEnumType(WeaponMasteryPropertyOrderField, {\n  name: 'WeaponMasteryPropertyOrderField',\n  description: 'Fields to sort Weapon Mastery Properties by'\n})\n\n@InputType()\nexport class WeaponMasteryPropertyOrder\n  implements BaseOrderInterface<WeaponMasteryPropertyOrderField>\n{\n  @Field(() => WeaponMasteryPropertyOrderField)\n  by!: WeaponMasteryPropertyOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => WeaponMasteryPropertyOrder, { nullable: true })\n  then_by?: WeaponMasteryPropertyOrder\n}\n\nexport const WeaponMasteryPropertyOrderSchema: z.ZodType<WeaponMasteryPropertyOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(WeaponMasteryPropertyOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: WeaponMasteryPropertyOrderSchema.optional()\n  })\n)\n\nexport const WeaponMasteryPropertyArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: WeaponMasteryPropertyOrderSchema.optional()\n})\n\nexport const WeaponMasteryPropertyIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class WeaponMasteryPropertyArgs extends BaseFilterArgs {\n  @Field(() => WeaponMasteryPropertyOrder, {\n    nullable: true,\n    description: 'Specify sorting order for weapon mastery properties.'\n  })\n  order?: WeaponMasteryPropertyOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/weaponMasteryProperty/resolver.ts",
    "content": "import { Arg, Args, Query, Resolver } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport WeaponMasteryPropertyModel, {\n  WeaponMasteryProperty2024\n} from '@/models/2024/weaponMasteryProperty'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  WEAPON_MASTERY_PROPERTY_SORT_FIELD_MAP,\n  WeaponMasteryPropertyArgs,\n  WeaponMasteryPropertyArgsSchema,\n  WeaponMasteryPropertyIndexArgsSchema,\n  WeaponMasteryPropertyOrderField\n} from './args'\n\n@Resolver(WeaponMasteryProperty2024)\nexport class WeaponMasteryPropertyResolver {\n  @Query(() => [WeaponMasteryProperty2024], {\n    description:\n      'Gets all weapon mastery properties, optionally filtered by name and sorted by name.'\n  })\n  async weaponMasteryProperties(\n    @Args(() => WeaponMasteryPropertyArgs) args: WeaponMasteryPropertyArgs\n  ): Promise<WeaponMasteryProperty2024[]> {\n    const validatedArgs = WeaponMasteryPropertyArgsSchema.parse(args)\n    const query = WeaponMasteryPropertyModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<WeaponMasteryPropertyOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: WEAPON_MASTERY_PROPERTY_SORT_FIELD_MAP,\n      defaultSortField: WeaponMasteryPropertyOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => WeaponMasteryProperty2024, {\n    nullable: true,\n    description: 'Gets a single weapon mastery property by index.'\n  })\n  async weaponMasteryProperty(\n    @Arg('index', () => String) indexInput: string\n  ): Promise<WeaponMasteryProperty2024 | null> {\n    const { index } = WeaponMasteryPropertyIndexArgsSchema.parse({ index: indexInput })\n    return WeaponMasteryPropertyModel.findOne({ index }).lean()\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/weaponProperty/args.ts",
    "content": "import { ArgsType, Field, InputType, registerEnumType } from 'type-graphql'\nimport { z } from 'zod'\n\nimport {\n  BaseFilterArgs,\n  BaseFilterArgsSchema,\n  BaseIndexArgsSchema,\n  BaseOrderInterface\n} from '@/graphql/common/args'\nimport { OrderByDirection } from '@/graphql/common/enums'\n\nexport enum WeaponPropertyOrderField {\n  NAME = 'name'\n}\n\nexport const WEAPON_PROPERTY_SORT_FIELD_MAP: Record<WeaponPropertyOrderField, string> = {\n  [WeaponPropertyOrderField.NAME]: 'name'\n}\n\nregisterEnumType(WeaponPropertyOrderField, {\n  name: 'WeaponPropertyOrderField',\n  description: 'Fields to sort Weapon Properties by'\n})\n\n@InputType()\nexport class WeaponPropertyOrder implements BaseOrderInterface<WeaponPropertyOrderField> {\n  @Field(() => WeaponPropertyOrderField)\n  by!: WeaponPropertyOrderField\n\n  @Field(() => OrderByDirection)\n  direction!: OrderByDirection\n\n  @Field(() => WeaponPropertyOrder, { nullable: true })\n  then_by?: WeaponPropertyOrder\n}\n\nexport const WeaponPropertyOrderSchema: z.ZodType<WeaponPropertyOrder> = z.lazy(() =>\n  z.object({\n    by: z.nativeEnum(WeaponPropertyOrderField),\n    direction: z.nativeEnum(OrderByDirection),\n    then_by: WeaponPropertyOrderSchema.optional()\n  })\n)\n\nexport const WeaponPropertyArgsSchema = z.object({\n  ...BaseFilterArgsSchema.shape,\n  order: WeaponPropertyOrderSchema.optional()\n})\n\nexport const WeaponPropertyIndexArgsSchema = BaseIndexArgsSchema\n\n@ArgsType()\nexport class WeaponPropertyArgs extends BaseFilterArgs {\n  @Field(() => WeaponPropertyOrder, {\n    nullable: true,\n    description: 'Specify sorting order for weapon properties.'\n  })\n  order?: WeaponPropertyOrder\n}\n"
  },
  {
    "path": "src/graphql/2024/resolvers/weaponProperty/resolver.ts",
    "content": "import { Arg, Args, Query, Resolver } from 'type-graphql'\n\nimport { buildSortPipeline } from '@/graphql/common/args'\nimport WeaponPropertyModel, { WeaponProperty2024 } from '@/models/2024/weaponProperty'\nimport { escapeRegExp } from '@/util'\n\nimport {\n  WEAPON_PROPERTY_SORT_FIELD_MAP,\n  WeaponPropertyArgs,\n  WeaponPropertyArgsSchema,\n  WeaponPropertyIndexArgsSchema,\n  WeaponPropertyOrderField\n} from './args'\n\n@Resolver(WeaponProperty2024)\nexport class WeaponPropertyResolver {\n  @Query(() => [WeaponProperty2024], {\n    description: 'Gets all weapon properties, optionally filtered by name and sorted by name.'\n  })\n  async weaponProperties(\n    @Args(() => WeaponPropertyArgs) args: WeaponPropertyArgs\n  ): Promise<WeaponProperty2024[]> {\n    const validatedArgs = WeaponPropertyArgsSchema.parse(args)\n    const query = WeaponPropertyModel.find()\n\n    if (validatedArgs.name != null && validatedArgs.name !== '') {\n      query.where({ name: { $regex: new RegExp(escapeRegExp(validatedArgs.name), 'i') } })\n    }\n\n    const sortQuery = buildSortPipeline<WeaponPropertyOrderField>({\n      order: validatedArgs.order,\n      sortFieldMap: WEAPON_PROPERTY_SORT_FIELD_MAP,\n      defaultSortField: WeaponPropertyOrderField.NAME\n    })\n\n    if (Object.keys(sortQuery).length > 0) {\n      query.sort(sortQuery)\n    }\n\n    if (validatedArgs.skip) {\n      query.skip(validatedArgs.skip)\n    }\n    if (validatedArgs.limit) {\n      query.limit(validatedArgs.limit)\n    }\n\n    return query.lean()\n  }\n\n  @Query(() => WeaponProperty2024, {\n    nullable: true,\n    description: 'Gets a single weapon property by index.'\n  })\n  async weaponProperty(\n    @Arg('index', () => String) indexInput: string\n  ): Promise<WeaponProperty2024 | null> {\n    const { index } = WeaponPropertyIndexArgsSchema.parse({ index: indexInput })\n    return WeaponPropertyModel.findOne({ index }).lean()\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/types/backgroundEquipment/choice.ts",
    "content": "import { Field, Int, ObjectType } from 'type-graphql'\n\nimport { EquipmentOptionSet2024 } from './optionSet'\n\n@ObjectType({ description: 'A top-level equipment choice for a 2024 background.' })\nexport class BackgroundEquipmentChoice2024 {\n  @Field(() => String, { nullable: true, description: 'Description of the choice.' })\n  desc?: string\n\n  @Field(() => Int, { description: 'Number to choose.' })\n  choose!: number\n\n  @Field(() => String, { description: \"The type of choice, e.g., 'equipment'.\" })\n  type!: string\n\n  @Field(() => EquipmentOptionSet2024, { description: 'The set of equipment options.' })\n  from!: EquipmentOptionSet2024\n}\n"
  },
  {
    "path": "src/graphql/2024/types/backgroundEquipment/common.ts",
    "content": "import { createUnionType, Field, Int, ObjectType } from 'type-graphql'\n\nimport { EquipmentCategory2024 } from '@/models/2024/equipmentCategory'\n\nimport { AnyEquipment } from '../../common/unions'\n\n// --- Money Option ---\n\n@ObjectType({ description: 'A money option within an equipment choice.' })\nexport class MoneyOption2024 {\n  @Field(() => String, { description: \"The type of this option, e.g., 'money'.\" })\n  option_type!: string\n\n  @Field(() => Int, { description: 'Amount of money.' })\n  count!: number\n\n  @Field(() => String, { description: \"Currency unit (e.g., 'gp').\" })\n  unit!: string\n}\n\n// --- Counted Reference Option ---\n\n@ObjectType({ description: 'A counted reference to a specific piece of equipment.' })\nexport class CountedReferenceOption2024 {\n  @Field(() => String, { description: \"The type of this option, e.g., 'counted_reference'.\" })\n  option_type!: string\n\n  @Field(() => Int, { description: 'Quantity of the referenced item.' })\n  count!: number\n\n  @Field(() => AnyEquipment, { description: 'The referenced equipment item.' })\n  of!: typeof AnyEquipment\n}\n\n// --- Equipment Category Set (used inside ChoiceItemOption2024) ---\n\n@ObjectType({ description: 'A set of equipment choices derived from an equipment category.' })\nexport class EquipmentCategorySet2024 {\n  @Field(() => String, {\n    description: \"Indicates the type of option set, e.g., 'equipment_category'.\"\n  })\n  option_set_type!: string\n\n  @Field(() => EquipmentCategory2024, { description: 'The equipment category to choose from.' })\n  equipment_category!: EquipmentCategory2024\n}\n\n@ObjectType({ description: 'Details of a nested choice limited to an equipment category.' })\nexport class EquipmentCategoryChoice2024 {\n  @Field(() => Int, { description: 'Number of items to choose from the category.' })\n  choose!: number\n\n  @Field(() => String, { description: \"Type of choice, e.g., 'equipment'.\" })\n  type!: string\n\n  @Field(() => EquipmentCategorySet2024, { description: 'The equipment category to choose from.' })\n  from!: EquipmentCategorySet2024\n}\n\n@ObjectType({ description: 'An option representing a choice from a single equipment category.' })\nexport class ChoiceItemOption2024 {\n  @Field(() => String, { description: \"The type of this option, e.g., 'choice'.\" })\n  option_type!: string\n\n  @Field(() => EquipmentCategoryChoice2024, {\n    description: 'The nested equipment category choice.'\n  })\n  choice!: EquipmentCategoryChoice2024\n}\n\n// --- Multiple Items Option ---\n\nexport const MultipleItemUnion2024 = createUnionType({\n  name: 'MultipleItemUnion2024',\n  description: 'An item within a multiple-items equipment option.',\n  types: () => [CountedReferenceOption2024, MoneyOption2024, ChoiceItemOption2024] as const,\n  resolveType(value) {\n    if (value.option_type === 'counted_reference') return CountedReferenceOption2024\n    if (value.option_type === 'money') return MoneyOption2024\n    if (value.option_type === 'choice') return ChoiceItemOption2024\n    return undefined\n  }\n})\n\n@ObjectType({ description: 'A bundle of multiple equipment items.' })\nexport class MultipleItemsOption2024 {\n  @Field(() => String, { description: \"The type of this option, e.g., 'multiple'.\" })\n  option_type!: string\n\n  @Field(() => [MultipleItemUnion2024], { description: 'The items included in this bundle.' })\n  items!: Array<typeof MultipleItemUnion2024>\n}\n"
  },
  {
    "path": "src/graphql/2024/types/backgroundEquipment/index.ts",
    "content": "export * from './choice'\nexport * from './common'\nexport * from './optionSet'\n"
  },
  {
    "path": "src/graphql/2024/types/backgroundEquipment/optionSet.ts",
    "content": "import { createUnionType, Field, ObjectType } from 'type-graphql'\n\nimport { CountedReferenceOption2024, MoneyOption2024, MultipleItemsOption2024 } from './common'\n\n@ObjectType({ description: 'A set of explicitly listed equipment options.' })\nexport class EquipmentOptionSet2024 {\n  @Field(() => String, { description: \"Indicates the type of option set, e.g., 'options_array'.\" })\n  option_set_type!: string\n\n  @Field(() => [EquipmentOptionUnion2024], { description: 'A list of specific equipment options.' })\n  options!: Array<typeof EquipmentOptionUnion2024>\n}\n\nexport const EquipmentOptionUnion2024 = createUnionType({\n  name: 'EquipmentOptionUnion2024',\n  description: 'An option within a background equipment choice.',\n  types: () => [CountedReferenceOption2024, MultipleItemsOption2024, MoneyOption2024] as const,\n  resolveType(value) {\n    if (value.option_type === 'counted_reference') return CountedReferenceOption2024\n    if (value.option_type === 'multiple') return MultipleItemsOption2024\n    if (value.option_type === 'money') return MoneyOption2024\n    return undefined\n  }\n})\n"
  },
  {
    "path": "src/graphql/2024/utils/backgroundEquipmentResolver.ts",
    "content": "import EquipmentModel from '@/models/2024/equipment'\nimport EquipmentCategoryModel, { EquipmentCategory2024 } from '@/models/2024/equipmentCategory'\nimport {\n  Choice,\n  ChoiceOption,\n  CountedReferenceOption,\n  EquipmentCategoryOptionSet,\n  MoneyOption,\n  MultipleOption,\n  OptionsArrayOptionSet\n} from '@/models/common/choice'\n\nimport { BackgroundEquipmentChoice2024 } from '../types/backgroundEquipment/choice'\nimport {\n  ChoiceItemOption2024,\n  CountedReferenceOption2024,\n  EquipmentCategoryChoice2024,\n  EquipmentCategorySet2024,\n  MoneyOption2024,\n  MultipleItemsOption2024\n} from '../types/backgroundEquipment/common'\nimport { EquipmentOptionSet2024 } from '../types/backgroundEquipment/optionSet'\n\n// --- Main entry point ---\n\nexport async function resolveBackgroundEquipmentChoices(\n  choices: Choice[] | undefined\n): Promise<BackgroundEquipmentChoice2024[]> {\n  if (!choices) return []\n\n  const resolved: BackgroundEquipmentChoice2024[] = []\n  for (const choice of choices) {\n    const resolvedChoice = await resolveBackgroundEquipmentChoice(choice)\n    if (resolvedChoice) resolved.push(resolvedChoice)\n  }\n  return resolved\n}\n\n// --- Single choice ---\n\nasync function resolveBackgroundEquipmentChoice(\n  choice: Choice\n): Promise<BackgroundEquipmentChoice2024 | null> {\n  const resolvedFrom = await resolveEquipmentOptionSet(choice.from as OptionsArrayOptionSet)\n  if (!resolvedFrom) return null\n\n  return {\n    desc: choice.desc,\n    choose: choice.choose,\n    type: choice.type,\n    from: resolvedFrom\n  }\n}\n\n// --- Option set ---\n\nasync function resolveEquipmentOptionSet(\n  optionSet: OptionsArrayOptionSet\n): Promise<EquipmentOptionSet2024 | null> {\n  const resolvedOptions: Array<\n    CountedReferenceOption2024 | MultipleItemsOption2024 | MoneyOption2024\n  > = []\n\n  for (const option of optionSet.options) {\n    const resolved = await resolveEquipmentOptionUnion(\n      option as CountedReferenceOption | MultipleOption | MoneyOption\n    )\n    if (resolved) resolvedOptions.push(resolved)\n  }\n\n  return {\n    option_set_type: optionSet.option_set_type,\n    options: resolvedOptions\n  }\n}\n\n// --- Top-level option union ---\n\nasync function resolveEquipmentOptionUnion(\n  option: CountedReferenceOption | MultipleOption | MoneyOption\n): Promise<CountedReferenceOption2024 | MultipleItemsOption2024 | MoneyOption2024 | null> {\n  switch (option.option_type) {\n    case 'counted_reference':\n      return resolveCountedReferenceOption(option as CountedReferenceOption)\n    case 'multiple':\n      return resolveMultipleItemsOption(option as MultipleOption)\n    case 'money':\n      return resolveMoneyOption(option as MoneyOption)\n    default:\n      console.warn(`Unknown top-level option_type: ${option.option_type}`)\n      return null\n  }\n}\n\n// --- Counted reference ---\n\nasync function resolveCountedReferenceOption(\n  option: CountedReferenceOption\n): Promise<CountedReferenceOption2024 | null> {\n  if (!option.of?.index) return null\n\n  const equipment = await EquipmentModel.findOne({ index: option.of.index }).lean()\n  if (!equipment) return null\n\n  return {\n    option_type: option.option_type,\n    count: option.count,\n    of: equipment\n  } as CountedReferenceOption2024\n}\n\n// --- Multiple items ---\n\nasync function resolveMultipleItemsOption(\n  option: MultipleOption\n): Promise<MultipleItemsOption2024 | null> {\n  if (!option.items?.length) return null\n\n  const resolvedItems: Array<CountedReferenceOption2024 | MoneyOption2024 | ChoiceItemOption2024> =\n    []\n\n  for (const item of option.items) {\n    let resolved: CountedReferenceOption2024 | MoneyOption2024 | ChoiceItemOption2024 | null = null\n\n    if (item.option_type === 'counted_reference') {\n      resolved = await resolveCountedReferenceOption(item as CountedReferenceOption)\n    } else if (item.option_type === 'money') {\n      resolved = resolveMoneyOption(item as MoneyOption)\n    } else if (item.option_type === 'choice') {\n      resolved = await resolveChoiceItemOption(item as ChoiceOption)\n    } else {\n      console.warn(`Unknown option_type within MultipleOption items: ${item.option_type}`)\n    }\n\n    if (resolved) resolvedItems.push(resolved)\n  }\n\n  return {\n    option_type: option.option_type,\n    items: resolvedItems\n  }\n}\n\n// --- Choice item (equipment category) ---\n\nasync function resolveChoiceItemOption(option: ChoiceOption): Promise<ChoiceItemOption2024 | null> {\n  const nestedChoice = option.choice\n  const nestedFrom = nestedChoice.from as EquipmentCategoryOptionSet\n\n  if (\n    nestedFrom.option_set_type !== 'equipment_category' ||\n    !nestedFrom.equipment_category?.index\n  ) {\n    console.warn('ChoiceOption.choice.from is not a valid EquipmentCategoryOptionSet:', nestedFrom)\n    return null\n  }\n\n  const equipmentCategory = await EquipmentCategoryModel.findOne({\n    index: nestedFrom.equipment_category.index\n  }).lean()\n\n  if (!equipmentCategory) {\n    console.warn(`Equipment category not found: ${nestedFrom.equipment_category.index}`)\n    return null\n  }\n\n  const categorySet: EquipmentCategorySet2024 = {\n    option_set_type: nestedFrom.option_set_type,\n    equipment_category: equipmentCategory as EquipmentCategory2024\n  }\n\n  const equipmentCategoryChoice: EquipmentCategoryChoice2024 = {\n    choose: nestedChoice.choose,\n    type: nestedChoice.type,\n    from: categorySet\n  }\n\n  return {\n    option_type: option.option_type,\n    choice: equipmentCategoryChoice\n  }\n}\n\n// --- Money ---\n\nfunction resolveMoneyOption(option: MoneyOption): MoneyOption2024 {\n  return {\n    option_type: option.option_type,\n    count: option.count,\n    unit: option.unit\n  }\n}\n"
  },
  {
    "path": "src/graphql/2024/utils/choiceResolvers.ts",
    "content": "import {\n  Proficiency2024Choice,\n  Proficiency2024ChoiceOption,\n  Proficiency2024ChoiceOptionSet,\n  ScorePrerequisiteChoice2024,\n  ScorePrerequisiteOption2024,\n  ScorePrerequisiteOptionSet2024\n} from '@/graphql/2024/common/choiceTypes'\nimport { resolveSingleReference } from '@/graphql/utils/resolvers'\nimport AbilityScoreModel, { AbilityScore2024 } from '@/models/2024/abilityScore'\nimport ProficiencyModel, { Proficiency2024 } from '@/models/2024/proficiency'\nimport {\n  Choice,\n  OptionsArrayOptionSet,\n  ReferenceOption,\n  ScorePrerequisiteOption\n} from '@/models/common/choice'\n\nexport async function resolveScorePrerequisiteChoice(\n  choiceData: Choice | undefined\n): Promise<ScorePrerequisiteChoice2024 | null> {\n  if (!choiceData) return null\n\n  const optionsArraySet = choiceData.from as OptionsArrayOptionSet\n  const resolvedOptions: ScorePrerequisiteOption2024[] = []\n\n  for (const dbOption of optionsArraySet.options) {\n    const dbScoreOpt = dbOption as ScorePrerequisiteOption\n    const abilityScore = await resolveSingleReference(dbScoreOpt.ability_score, AbilityScoreModel)\n    if (abilityScore !== null) {\n      resolvedOptions.push({\n        option_type: dbScoreOpt.option_type,\n        ability_score: abilityScore as AbilityScore2024,\n        minimum_score: dbScoreOpt.minimum_score\n      })\n    }\n  }\n\n  return {\n    desc: choiceData.desc,\n    choose: choiceData.choose,\n    type: choiceData.type,\n    from: {\n      option_set_type: optionsArraySet.option_set_type,\n      options: resolvedOptions\n    } as ScorePrerequisiteOptionSet2024\n  }\n}\n\nexport async function resolveProficiency2024Choice(\n  choiceData: Choice | undefined\n): Promise<Proficiency2024Choice | null> {\n  if (!choiceData) return null\n\n  const optionsArraySet = choiceData.from as OptionsArrayOptionSet\n  const resolvedOptions: Proficiency2024ChoiceOption[] = []\n\n  for (const dbOption of optionsArraySet.options) {\n    const dbRefOpt = dbOption as ReferenceOption\n    const proficiency = await resolveSingleReference(dbRefOpt.item, ProficiencyModel)\n    if (proficiency !== null) {\n      resolvedOptions.push({\n        option_type: dbRefOpt.option_type,\n        item: proficiency as Proficiency2024\n      })\n    }\n  }\n\n  return {\n    desc: choiceData.desc,\n    choose: choiceData.choose,\n    type: choiceData.type,\n    from: {\n      option_set_type: optionsArraySet.option_set_type,\n      options: resolvedOptions\n    } as Proficiency2024ChoiceOptionSet\n  }\n}\n\nexport async function resolveProficiency2024ChoiceArray(\n  choices: Choice[] | undefined\n): Promise<Proficiency2024Choice[]> {\n  if (!choices || !Array.isArray(choices)) return []\n\n  const resolved: Proficiency2024Choice[] = []\n  for (const choice of choices) {\n    const resolvedChoice = await resolveProficiency2024Choice(choice)\n    if (resolvedChoice) resolved.push(resolvedChoice)\n  }\n  return resolved\n}\n"
  },
  {
    "path": "src/graphql/2024/utils/resolvers.ts",
    "content": ""
  },
  {
    "path": "src/graphql/common/args.ts",
    "content": "import { ArgsType, Field, Int } from 'type-graphql'\nimport { z } from 'zod'\n\nimport { OrderByDirection } from '@/graphql/common/enums'\n\n// --- Pagination ---\n\nexport const BasePaginationArgsSchema = z.object({\n  skip: z.number().int().min(0).optional().default(0),\n  limit: z.number().int().min(1).optional().default(100) // Default 50, Max 100\n})\n\n@ArgsType()\nexport class BasePaginationArgs {\n  @Field(() => Int, {\n    nullable: true,\n    description: 'Number of results to skip for pagination. Default: 0.'\n  })\n  skip?: number\n\n  @Field(() => Int, {\n    nullable: true,\n    description: 'Maximum number of results to return for pagination. Default: 50, Max: 100.'\n  })\n  limit?: number\n}\n\n// --- Filtering & Sorting by Name (includes Pagination) ---\n\nexport const BaseFilterArgsSchema = z.object({\n  ...BasePaginationArgsSchema.shape,\n  name: z.string().optional()\n})\n\n@ArgsType()\nexport class BaseFilterArgs extends BasePaginationArgs {\n  @Field(() => String, {\n    nullable: true,\n    description: 'Filter by name (case-insensitive, partial match).'\n  })\n  name?: string\n}\n\n// --- Index Argument ---\n\nexport const BaseIndexArgsSchema = z.object({\n  index: z.string().min(1, { message: 'Index must be a non-empty string' })\n})\n\n// --- Generic Sorting Logic ---\n\nexport interface BaseOrderInterface<TOrderFieldValue extends string> {\n  by: TOrderFieldValue\n  direction: OrderByDirection\n  then_by?: BaseOrderInterface<TOrderFieldValue>\n}\n\ninterface BuildSortPipelineArgs<TOrderFieldValue extends string> {\n  order?: BaseOrderInterface<TOrderFieldValue>\n  sortFieldMap: Record<string, string>\n  defaultSortField?: TOrderFieldValue\n  defaultSortDirection?: OrderByDirection\n}\n\nexport function buildSortPipeline<TOrderFieldValue extends string>({\n  order,\n  sortFieldMap,\n  defaultSortField,\n  defaultSortDirection = OrderByDirection.ASC\n}: BuildSortPipelineArgs<TOrderFieldValue>): Record<string, 1 | -1> {\n  const sortPipeline: Record<string, 1 | -1> = {}\n\n  function populateSortPipelineRecursive(\n    currentOrder: BaseOrderInterface<TOrderFieldValue> | undefined\n  ) {\n    if (!currentOrder) {\n      return\n    }\n\n    const fieldKey = currentOrder.by\n    const mappedDbField = sortFieldMap[fieldKey]\n\n    if (mappedDbField) {\n      const direction = currentOrder.direction === OrderByDirection.ASC ? 1 : -1\n      sortPipeline[mappedDbField] = direction\n    } else {\n      console.warn(\n        `Sort field for key \"${fieldKey}\" not found in sortFieldMap. Skipping this criterion.`\n      )\n    }\n\n    if (currentOrder.then_by) {\n      populateSortPipelineRecursive(currentOrder.then_by)\n    }\n  }\n\n  populateSortPipelineRecursive(order)\n\n  if (Object.keys(sortPipeline).length === 0 && defaultSortField != null) {\n    const defaultFieldKey = String(defaultSortField)\n    const defaultMappedField = sortFieldMap[defaultFieldKey]\n    if (defaultMappedField) {\n      sortPipeline[defaultMappedField] = defaultSortDirection === OrderByDirection.ASC ? 1 : -1\n    } else {\n      console.warn(\n        `Default sort field for key \"${defaultFieldKey}\" not found in sortFieldMap. No default sort will be applied.`\n      )\n    }\n  }\n\n  return sortPipeline\n}\n"
  },
  {
    "path": "src/graphql/common/choiceTypes.ts",
    "content": "import { Field, Int, ObjectType } from 'type-graphql'\n\n// --- Generic String Choice Types ---\n@ObjectType({\n  description: 'Represents a single string option within a choice (e.g., a flaw, a bond).'\n})\nexport class StringChoiceOption {\n  @Field(() => String, { description: 'The text content of the string option.' })\n  string!: string\n\n  @Field(() => String, { description: 'The type of the string option.' })\n  option_type!: string\n}\n\n@ObjectType({ description: 'Represents a set of string options.' })\nexport class StringChoiceOptionSet {\n  @Field(() => String, { description: 'The type of the string option set.' })\n  option_set_type!: string\n\n  @Field(() => [StringChoiceOption], { description: 'The list of string options available.' })\n  options!: StringChoiceOption[]\n}\n\n@ObjectType({ description: 'Represents a choice from a list of string options.' })\nexport class StringChoice {\n  @Field(() => Int, { description: 'The number of options to choose from this list.' })\n  choose!: number\n\n  @Field(() => String, { description: 'The type or category of the choice.' })\n  type!: string\n\n  @Field(() => StringChoiceOptionSet, { description: 'The set of string options available.' })\n  from!: StringChoiceOptionSet\n}\n"
  },
  {
    "path": "src/graphql/common/enums.ts",
    "content": "import { registerEnumType } from 'type-graphql'\n\nexport enum OrderByDirection {\n  ASC = 'ASC',\n  DESC = 'DESC'\n}\n\nregisterEnumType(OrderByDirection, {\n  name: 'OrderByDirection',\n  description: 'Specifies the direction for ordering results.'\n})\n"
  },
  {
    "path": "src/graphql/common/inputs.ts",
    "content": "import { Field, InputType, Int } from 'type-graphql'\nimport { z } from 'zod'\n\n// Zod schema for NumberRangeFilterInput\nexport const NumberRangeFilterInputSchema = z.object({\n  lt: z.number().int().optional(),\n  lte: z.number().int().optional(),\n  gt: z.number().int().optional(),\n  gte: z.number().int().optional()\n})\n\n@InputType({\n  description:\n    'Input for filtering integer fields, allowing exact match, a list of matches, or a range.'\n})\nexport class NumberRangeFilterInput {\n  @Field(() => Int, { nullable: true, description: 'Matches values less than.' })\n  lt?: number\n\n  @Field(() => Int, { nullable: true, description: 'Matches values less than or equal to.' })\n  lte?: number\n\n  @Field(() => Int, { nullable: true, description: 'Matches values greater than.' })\n  gt?: number\n\n  @Field(() => Int, { nullable: true, description: 'Matches values greater than or equal to.' })\n  gte?: number\n}\n\n// Zod schema for NumberFilterInput\nexport const NumberFilterInputSchema = z.object({\n  eq: z.number().int().optional(),\n  in: z.array(z.number().int()).optional(),\n  nin: z.array(z.number().int()).optional(),\n  range: NumberRangeFilterInputSchema.optional()\n})\n\n@InputType({\n  description: 'Input for filtering by an integer, an array of integers, or a range of integers.'\n})\nexport class NumberFilterInput {\n  @Field(() => Int, { nullable: true, description: 'Matches an exact integer value.' })\n  eq?: number;\n\n  @Field(() => [Int], {\n    nullable: true,\n    description: 'Matches any integer value in the provided list.'\n  })\n  in?: number[]\n\n  @Field(() => [Int], {\n    nullable: true,\n    description: 'Matches no integer value in the provided list.'\n  })\n  nin?: number[]\n\n  @Field(() => NumberRangeFilterInput, {\n    nullable: true,\n    description: 'Matches integer values within a specified range.'\n  })\n  range?: NumberRangeFilterInput\n}\n\n/**\n * Builds a MongoDB query object from a NumberFilterInput.\n * @param filterInput The NumberFilterInput object.\n * @returns A MongoDB query object for the number field, or null if the input is empty or invalid.\n */\nexport function buildMongoQueryFromNumberFilter(\n  filterInput?: NumberFilterInput\n): Record<string, any> | null {\n  if (!filterInput) {\n    return null\n  }\n\n  const queryPortion: any = {}\n\n  if (typeof filterInput.eq === 'number') {\n    queryPortion.$eq = filterInput.eq\n  }\n  if (Array.isArray(filterInput.in) && filterInput.in.length > 0) {\n    queryPortion.$in = filterInput.in\n  }\n  if (Array.isArray(filterInput.nin) && filterInput.nin.length > 0) {\n    queryPortion.$nin = filterInput.nin\n  }\n  if (filterInput.range) {\n    if (typeof filterInput.range.lt === 'number') queryPortion.$lt = filterInput.range.lt\n    if (typeof filterInput.range.lte === 'number') queryPortion.$lte = filterInput.range.lte\n    if (typeof filterInput.range.gt === 'number') queryPortion.$gt = filterInput.range.gt\n    if (typeof filterInput.range.gte === 'number') queryPortion.$gte = filterInput.range.gte\n  }\n\n  if (Object.keys(queryPortion).length === 0) {\n    return null\n  }\n\n  return queryPortion\n}\n"
  },
  {
    "path": "src/graphql/common/types.ts",
    "content": "import { Field, Int, ObjectType } from 'type-graphql'\n\n@ObjectType({ description: 'A key-value pair representing a value at a specific level.' })\nexport class LevelValue {\n  @Field(() => Int, { description: 'The level.' })\n  level!: number\n\n  @Field(() => String, { description: 'The value associated with the level.' })\n  value!: string\n}\n\n@ObjectType({ description: 'Represents a count of spell slots for a specific level.' })\nexport class SpellSlotCount {\n  @Field(() => Int, { description: 'The spell slot level.' })\n  slot_level!: number\n\n  @Field(() => Int, { description: 'The number of spell slots available for this level.' })\n  count!: number\n}\n"
  },
  {
    "path": "src/graphql/utils/resolvers.ts",
    "content": "import { ReturnModelType } from '@typegoose/typegoose'\nimport { AnyParamConstructor } from '@typegoose/typegoose/lib/types'\n\nimport {\n  StringChoice,\n  StringChoiceOption,\n  StringChoiceOptionSet\n} from '@/graphql/common/choiceTypes'\nimport { APIReference } from '@/models/common/apiReference'\nimport {\n  Choice,\n  OptionsArrayOptionSet,\n  ReferenceOption,\n  StringOption\n} from '@/models/common/choice'\n\nexport async function resolveSingleReference<T>(\n  reference: APIReference | null | undefined,\n  TargetModel: ReturnModelType<AnyParamConstructor<T>>\n): Promise<any | null> {\n  if (reference == null || reference.index == null || reference.index === '') {\n    return null\n  }\n  return TargetModel.findOne({ index: reference.index }).lean() as any\n}\n\nexport async function resolveMultipleReferences<T>(\n  references: APIReference[] | null | undefined,\n  TargetModel: ReturnModelType<AnyParamConstructor<T>>\n): Promise<any[]> {\n  if (!references || references.length === 0) {\n    return []\n  }\n\n  const indices = references.map((ref) => ref.index)\n  return TargetModel.find({ index: { $in: indices } }).lean() as any\n}\n\nexport async function resolveReferenceOptionArray<\n  TItem,\n  TGqlItemChoiceOption extends { option_type: string; item: TItem }\n>(\n  optionsArraySet: OptionsArrayOptionSet,\n  ItemModel: ReturnModelType<AnyParamConstructor<TItem>>,\n  createGqlOption: (item: TItem, optionType: string) => TGqlItemChoiceOption\n): Promise<TGqlItemChoiceOption[]> {\n  const resolvedEmbeddedOptions: TGqlItemChoiceOption[] = []\n  for (const dbOption of optionsArraySet.options) {\n    const dbRefOpt = dbOption as ReferenceOption\n    const resolvedItem = await resolveSingleReference(dbRefOpt.item, ItemModel)\n    if (resolvedItem !== null) {\n      resolvedEmbeddedOptions.push(createGqlOption(resolvedItem as TItem, dbRefOpt.option_type))\n    }\n  }\n  return resolvedEmbeddedOptions\n}\n\nexport function resolveStringChoice(choiceData: Choice): StringChoice {\n  const dbOptionSet = choiceData.from as OptionsArrayOptionSet\n\n  const gqlChoiceOptions: StringChoiceOption[] = []\n  for (const dbOption of dbOptionSet.options) {\n    const dbStringOpt = dbOption as StringOption\n    gqlChoiceOptions.push({\n      string: dbStringOpt.string,\n      option_type: dbStringOpt.option_type || 'string' // Fallback for option_type\n    })\n  }\n\n  const gqlOptionSet: StringChoiceOptionSet = {\n    option_set_type: dbOptionSet.option_set_type,\n    options: gqlChoiceOptions\n  }\n\n  return {\n    choose: choiceData.choose,\n    type: choiceData.type,\n    from: gqlOptionSet\n  }\n}\n"
  },
  {
    "path": "src/middleware/apolloServer.ts",
    "content": "import { ApolloServer } from '@apollo/server'\nimport { ApolloServerPluginCacheControl } from '@apollo/server/plugin/cacheControl'\nimport depthLimit from 'graphql-depth-limit'\n\nconst createApolloMiddleware = async (schema: any) => {\n  const server = new ApolloServer({\n    schema,\n    plugins: [\n      ApolloServerPluginCacheControl({\n        // Cache everything for 1 second by default.\n        defaultMaxAge: 3600,\n        // Don't send the `cache-control` response header.\n        calculateHttpHeaders: false\n      })\n    ],\n    introspection: true,\n    validationRules: [depthLimit(20)]\n  })\n  return server\n}\n\nexport { createApolloMiddleware }\n"
  },
  {
    "path": "src/middleware/bugsnag.ts",
    "content": "import bugsnag from '@bugsnag/js'\nimport bugsnagExpress from '@bugsnag/plugin-express'\n\nimport { bugsnagApiKey } from '@/util'\n\nconst createBugsnagMiddleware = () => {\n  if (bugsnagApiKey == null || bugsnagApiKey === '') {\n    return null\n  }\n\n  const bugsnagClient = bugsnag.start({\n    apiKey: bugsnagApiKey,\n    plugins: [bugsnagExpress]\n  })\n\n  return bugsnagClient.getPlugin('express')\n}\n\nconst bugsnagMiddleware = createBugsnagMiddleware()\n\nexport default bugsnagMiddleware\n"
  },
  {
    "path": "src/middleware/errorHandler.ts",
    "content": "import { NextFunction, Request, Response } from 'express'\n\n/* eslint-disable @typescript-eslint/no-unused-vars */\nconst errorHandler = (err: any, req: Request, res: Response, next: NextFunction): void => {\n  console.error(err.stack)\n\n  const statusCode = typeof err.status === 'number' ? err.status : 404\n  res.status(statusCode).json({\n    message: err.message\n  })\n}\n\nexport default errorHandler\n"
  },
  {
    "path": "src/models/2014/abilityScore.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { Skill } from './skill'\n\n@ObjectType({\n  description:\n    'An ability score representing a fundamental character attribute (e.g., Strength, Dexterity).'\n})\n@srdModelOptions('2014-ability-scores')\nexport class AbilityScore {\n  @Field(() => [String], {\n    description: 'A description of the ability score and its applications.'\n  })\n  @prop({ required: true, index: true, type: () => [String] })\n  public desc!: string[]\n\n  @Field(() => String, { description: 'The full name of the ability score (e.g., Strength).' })\n  @prop({ required: true, index: true, type: () => String })\n  public full_name!: string\n\n  @Field(() => String, { description: 'The unique identifier for this ability score (e.g., str).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The abbreviated name of the ability score (e.g., STR).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => [Skill], { description: 'Skills associated with this ability score.' })\n  @prop({ type: () => [APIReference] })\n  public skills!: APIReference[]\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type AbilityScoreDocument = DocumentType<AbilityScore>\nconst AbilityScoreModel = getModelForClass(AbilityScore)\n\nexport default AbilityScoreModel\n"
  },
  {
    "path": "src/models/2014/alignment.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({ description: \"Represents a creature's moral and ethical outlook.\" })\n@srdModelOptions('2014-alignments')\nexport class Alignment {\n  @Field(() => String, { description: 'A brief description of the alignment.' })\n  @prop({ required: true, index: true, type: () => String })\n  public desc!: string\n\n  @Field(() => String, {\n    description: 'A shortened representation of the alignment (e.g., LG, CE).'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public abbreviation!: string\n\n  @Field(() => String, {\n    description: 'The unique identifier for this alignment (e.g., lawful-good).'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, {\n    description: 'The name of the alignment (e.g., Lawful Good, Chaotic Evil).'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type AlignmentDocument = DocumentType<Alignment>\nconst AlignmentModel = getModelForClass(Alignment)\nexport default AlignmentModel\n"
  },
  {
    "path": "src/models/2014/background.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, Int, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { Choice } from '@/models/common/choice'\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { Equipment } from './equipment'\nimport { Proficiency } from './proficiency'\n\n@ObjectType({ description: 'Reference to a piece of equipment with a quantity.' })\nexport class EquipmentRef {\n  @Field(() => Equipment, { description: 'The specific equipment referenced.' })\n  @prop({ type: () => APIReference })\n  public equipment!: APIReference\n\n  @Field(() => Int, { description: 'The quantity of the referenced equipment.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public quantity!: number\n}\n\n@ObjectType({ description: 'A special feature granted by the background.' })\nclass BackgroundFeature {\n  @Field(() => String, { description: 'The name of the background feature.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => [String], { description: 'The description of the background feature.' })\n  @prop({ required: true, index: true, type: () => [String] })\n  public desc!: string[]\n}\n\n@ObjectType({\n  description: 'Represents a character background providing flavor, proficiencies, and features.'\n})\n@srdModelOptions('2014-backgrounds')\nexport class Background {\n  @Field(() => String, {\n    description: 'The unique identifier for this background (e.g., acolyte).'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the background (e.g., Acolyte).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => [Proficiency], { description: 'Proficiencies granted by this background at start.' })\n  @prop({ type: () => [APIReference] })\n  public starting_proficiencies!: APIReference[]\n\n  // Handled by BackgroundResolver\n  @prop({ type: () => Choice })\n  public language_options!: Choice\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => [EquipmentRef], { description: 'Equipment received when choosing this background.' })\n  @prop({ type: () => [EquipmentRef] })\n  public starting_equipment!: EquipmentRef[]\n\n  // Handled by BackgroundResolver\n  @prop({ type: () => [Choice], index: true })\n  public starting_equipment_options!: Choice[]\n\n  @Field(() => BackgroundFeature, { description: 'The feature associated with this background.' })\n  @prop({ type: () => BackgroundFeature })\n  public feature!: BackgroundFeature\n\n  // Handled by BackgroundResolver\n  @prop({ type: () => Choice })\n  public personality_traits!: Choice\n\n  // Handled by BackgroundResolver\n  @prop({ type: () => Choice })\n  public ideals!: Choice\n\n  // Handled by BackgroundResolver\n  @prop({ type: () => Choice })\n  public bonds!: Choice\n\n  // Handled by BackgroundResolver\n  @prop({ type: () => Choice })\n  public flaws!: Choice\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type BackgroundDocument = DocumentType<Background>\nconst BackgroundModel = getModelForClass(Background)\n\nexport default BackgroundModel\n"
  },
  {
    "path": "src/models/2014/class.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, Int, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { Choice } from '@/models/common/choice'\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { AbilityScore } from './abilityScore'\nimport { Level } from './level'\nimport { Proficiency } from './proficiency'\nimport { Spell } from './spell'\nimport { Subclass } from './subclass'\n\n@ObjectType({ description: 'Starting equipment item for a class' })\nexport class ClassEquipment {\n  // Handled by ClassEquipmentResolver\n  @prop({ type: () => APIReference })\n  public equipment!: APIReference\n\n  @Field(() => Int, { description: 'Quantity of the equipment item.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public quantity!: number\n}\n\n@ObjectType({ description: \"Information about a class's spellcasting ability\" })\nexport class SpellcastingInfo {\n  @Field(() => [String], { description: 'Description of the spellcasting ability.' })\n  @prop({ required: true, index: true, type: () => [String] })\n  public desc!: string[]\n\n  @Field(() => String, { description: 'Name of the spellcasting ability.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n}\n\n@ObjectType({ description: 'Spellcasting details for a class' })\nexport class Spellcasting {\n  @Field(() => [SpellcastingInfo], { description: 'Spellcasting details for the class.' })\n  @prop({ type: () => [SpellcastingInfo] })\n  public info!: SpellcastingInfo[]\n\n  @Field(() => Int, { description: 'Level of the spellcasting ability.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public level!: number\n\n  @Field(() => AbilityScore, { description: 'Ability score used for spellcasting.' })\n  @prop({ type: () => APIReference })\n  public spellcasting_ability!: APIReference\n}\n\n@ObjectType({ description: 'Prerequisite for multi-classing' })\nexport class MultiClassingPrereq {\n  @Field(() => AbilityScore, { nullable: true, description: 'The ability score required.' })\n  @prop({ type: () => APIReference })\n  public ability_score!: APIReference\n\n  @Field(() => Int, { description: 'The minimum score required.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public minimum_score!: number\n}\n\n@ObjectType({ description: 'Multi-classing requirements and features for a class' })\nexport class MultiClassing {\n  @Field(() => [MultiClassingPrereq], {\n    nullable: true,\n    description: 'Ability score prerequisites for multi-classing.'\n  })\n  @prop({ type: () => [MultiClassingPrereq], default: undefined })\n  public prerequisites?: MultiClassingPrereq[]\n\n  // Handled by MultiClassingResolver\n  @prop({ type: () => Choice, default: undefined })\n  public prerequisite_options?: Choice\n\n  @Field(() => [Proficiency], {\n    nullable: true,\n    description: 'Proficiencies gained when multi-classing into this class.'\n  })\n  @prop({ type: () => [APIReference], default: undefined })\n  public proficiencies?: APIReference[]\n\n  // Handled by MultiClassingResolver\n  @prop({ type: () => [Choice], default: undefined })\n  public proficiency_choices?: Choice[]\n}\n\n@ObjectType({ description: 'Represents a character class (e.g., Barbarian, Wizard)' })\n@srdModelOptions('2014-classes')\nexport class Class {\n  @Field(() => [Level], {\n    description: 'All levels for this class, detailing features and abilities gained.'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public class_levels!: string\n\n  @Field(() => MultiClassing, {\n    nullable: true,\n    description: 'Multi-classing requirements and features for this class.'\n  })\n  @prop({ type: () => MultiClassing })\n  public multi_classing!: MultiClassing\n\n  @Field(() => Int, { description: 'Hit die size for the class (e.g., 6, 8, 10, 12)' })\n  @prop({ required: true, index: true, type: () => Number })\n  public hit_die!: number\n\n  @Field(() => String, { description: 'Unique identifier for the class' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'Name of the class' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => [Proficiency], {\n    nullable: true,\n    description: 'Base proficiencies granted by this class.'\n  })\n  @prop({ type: () => [APIReference] })\n  public proficiencies!: APIReference[]\n\n  // Handled by ClassResolver\n  @prop({ type: () => [Choice] })\n  public proficiency_choices!: Choice[]\n\n  @Field(() => [AbilityScore], {\n    nullable: true,\n    description: 'Saving throw proficiencies granted by this class.'\n  })\n  @prop({ type: () => [APIReference] })\n  public saving_throws!: APIReference[]\n\n  @Field(() => Spellcasting, {\n    nullable: true,\n    description: 'Spellcasting details for the class.'\n  })\n  @prop({ type: () => Spellcasting })\n  public spellcasting?: Spellcasting\n\n  @Field(() => [Spell], { description: 'Spells available to this class.' })\n  @prop({ required: true, index: true, type: () => String })\n  public spells!: string\n\n  @Field(() => [ClassEquipment], {\n    nullable: true,\n    description: 'Starting equipment for the class.'\n  })\n  @prop({ type: () => [ClassEquipment] })\n  public starting_equipment!: ClassEquipment[]\n\n  // Handled by ClassResolver\n  @prop({ type: () => [Choice] })\n  public starting_equipment_options!: Choice[]\n\n  @Field(() => [Subclass], { nullable: true, description: 'Available subclasses for this class.' })\n  @prop({ type: () => [APIReference] })\n  public subclasses!: APIReference[]\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type ClassDocument = DocumentType<Class>\nconst ClassModel = getModelForClass(Class)\nexport default ClassModel\n"
  },
  {
    "path": "src/models/2014/collection.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\n\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@srdModelOptions('2014-collections')\nexport class Collection {\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n}\n\nexport type CollectionDocument = DocumentType<Collection>\nconst CollectionModel = getModelForClass(Collection)\n\nexport default CollectionModel\n"
  },
  {
    "path": "src/models/2014/condition.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({ description: 'A state that can affect a creature, such as Blinded or Prone.' })\n@srdModelOptions('2014-conditions')\nexport class Condition {\n  @Field(() => String, { description: 'The unique identifier for this condition (e.g., blinded).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the condition (e.g., Blinded).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => [String], { description: 'A description of the effects of the condition.' })\n  @prop({ required: true, type: () => [String] })\n  public desc!: string[]\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type ConditionDocument = DocumentType<Condition>\nconst ConditionModel = getModelForClass(Condition)\n\nexport default ConditionModel\n"
  },
  {
    "path": "src/models/2014/damageType.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({ description: 'Represents a type of damage (e.g., Acid, Bludgeoning, Fire).' })\n@srdModelOptions('2014-damage-types')\nexport class DamageType {\n  @Field(() => String, { description: 'The unique identifier for this damage type (e.g., acid).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the damage type (e.g., Acid).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => [String], { description: 'A description of the damage type.' })\n  @prop({ required: true, type: () => [String] })\n  public desc!: string[]\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type DamageTypeDocument = DocumentType<DamageType>\nconst DamageTypeModel = getModelForClass(DamageType)\n\nexport default DamageTypeModel\n"
  },
  {
    "path": "src/models/2014/equipment.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, Float, Int, ObjectType } from 'type-graphql'\n\nimport { IEquipment } from '@/graphql/2014/common/interfaces'\nimport { EquipmentCategory } from '@/models/2014/equipmentCategory'\nimport { APIReference } from '@/models/common/apiReference'\nimport { Damage } from '@/models/common/damage'\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({ description: 'Details about armor class.' })\nexport class ArmorClass {\n  @Field(() => Int, { description: 'Base armor class value.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public base!: number\n\n  @Field(() => Boolean, { description: 'Indicates if Dexterity bonus applies.' })\n  @prop({ required: true, index: true, type: () => Boolean })\n  public dex_bonus!: boolean\n\n  @Field(() => Int, { nullable: true, description: 'Maximum Dexterity bonus allowed.' })\n  @prop({ index: true, type: () => Number })\n  public max_bonus?: number\n}\n\n@ObjectType({ description: 'An item and its quantity within a container or bundle.' })\nexport class Content {\n  // Handled by ContentFieldResolver\n  @prop({ type: () => APIReference })\n  public item!: APIReference\n\n  @Field(() => Int, { description: 'The quantity of the item.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public quantity!: number\n}\n\n@ObjectType({ description: 'Cost of an item in coinage.' })\nexport class Cost {\n  @Field(() => Int, { description: 'The quantity of coins.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public quantity!: number\n\n  @Field(() => String, { description: 'The unit of coinage (e.g., gp, sp, cp).' })\n  @prop({ required: true, index: true, type: () => String })\n  public unit!: string\n}\n\n@ObjectType({ description: 'Range of a weapon (normal and long).' })\nexport class Range {\n  @Field(() => Int, { nullable: true, description: 'The long range of the weapon.' })\n  @prop({ index: true, type: () => Number })\n  public long?: number\n\n  @Field(() => Int, { description: 'The normal range of the weapon.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public normal!: number\n}\n\n@ObjectType({ description: 'Speed of a mount or vehicle.' })\nexport class Speed {\n  @Field(() => Float, { description: 'The speed quantity.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public quantity!: number\n\n  @Field(() => String, { description: 'The unit of speed (e.g., ft./round).' })\n  @prop({ required: true, index: true, type: () => String })\n  public unit!: string\n}\n\n@ObjectType({ description: 'Range for a thrown weapon.' })\nexport class ThrowRange {\n  @Field(() => Int, { description: 'The long range when thrown.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public long!: number\n\n  @Field(() => Int, { description: 'The normal range when thrown.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public normal!: number\n}\n\n@ObjectType({\n  description: 'Base Equipment class for common fields, potentially used in Unions.'\n})\n@srdModelOptions('2014-equipment')\nexport class Equipment implements IEquipment {\n  // General fields\n\n  @Field(() => String, { description: 'The unique identifier for this equipment.' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the equipment.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => [String], { nullable: true, description: 'Description of the equipment.' })\n  @prop({ required: true, index: true, type: () => [String] })\n  public desc?: string[]\n\n  @Field(() => EquipmentCategory, { description: 'The category this equipment belongs to.' })\n  @prop({ type: () => APIReference })\n  public equipment_category!: APIReference\n\n  @Field(() => EquipmentCategory, {\n    nullable: true,\n    description: 'Category if the equipment is gear.'\n  })\n  @prop({ type: () => APIReference })\n  public gear_category?: APIReference\n\n  @Field(() => Cost, { description: 'Cost of the equipment in coinage.' })\n  @prop({ type: () => Cost })\n  public cost!: Cost\n\n  @Field(() => Float, { nullable: true, description: 'Weight of the equipment in pounds.' })\n  @prop({ index: true, type: () => Number })\n  public weight?: number\n\n  // Specific fields\n  @prop({ index: true, type: () => String })\n  public armor_category?: string\n\n  @prop({ type: () => ArmorClass })\n  public armor_class?: ArmorClass\n\n  @prop({ index: true, type: () => String })\n  public capacity?: string\n\n  @prop({ index: true, type: () => String })\n  public category_range?: string\n\n  @prop({ type: () => [Content] })\n  public contents?: Content[]\n\n  @prop({ type: () => Damage })\n  public damage?: Damage\n\n  @prop({ index: true, type: () => String })\n  public image?: string\n\n  @prop({ type: () => [APIReference] })\n  public properties?: APIReference[]\n\n  @prop({ index: true, type: () => Number })\n  public quantity?: number\n\n  @prop({ type: () => Range })\n  public range?: Range\n\n  @prop({ index: true, type: () => [String] })\n  public special?: string[]\n\n  @prop({ type: () => Speed })\n  public speed?: Speed\n\n  @prop({ index: true, type: () => Boolean })\n  public stealth_disadvantage?: boolean\n\n  @prop({ index: true, type: () => Number })\n  public str_minimum?: number\n\n  @prop({ type: () => ThrowRange })\n  public throw_range?: ThrowRange\n\n  @prop({ index: true, type: () => String })\n  public tool_category?: string\n\n  @prop({ type: () => Damage })\n  public two_handed_damage?: Damage\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @prop({ index: true, type: () => String })\n  public vehicle_category?: string\n\n  @prop({ index: true, type: () => String })\n  public weapon_category?: string\n\n  @prop({ index: true, type: () => String })\n  public weapon_range?: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type EquipmentDocument = DocumentType<Equipment>\nconst EquipmentModel = getModelForClass(Equipment)\n\nexport default EquipmentModel\n"
  },
  {
    "path": "src/models/2014/equipmentCategory.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({\n  description: 'A category for grouping equipment (e.g., Weapon, Armor, Adventuring Gear).'\n})\n@srdModelOptions('2014-equipment-categories')\nexport class EquipmentCategory {\n  // Handled by EquipmentCategoryResolver\n  @prop({ type: () => [APIReference], index: true })\n  public equipment!: APIReference[]\n\n  @Field(() => String, { description: 'The unique identifier for this category (e.g., weapon).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the category (e.g., Weapon).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type EquipmentCategoryDocument = DocumentType<EquipmentCategory>\nconst EquipmentCategoryModel = getModelForClass(EquipmentCategory)\n\nexport default EquipmentCategoryModel\n"
  },
  {
    "path": "src/models/2014/feat.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, Int, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { AbilityScore } from './abilityScore'\n\n@ObjectType({ description: 'A prerequisite for taking a feat, usually a minimum ability score.' })\nexport class Prerequisite {\n  @Field(() => AbilityScore, {\n    nullable: true,\n    description: 'The ability score required for this prerequisite.'\n  })\n  @prop({ type: () => APIReference })\n  public ability_score!: APIReference\n\n  @Field(() => Int, { description: 'The minimum score required in the referenced ability score.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public minimum_score!: number\n}\n\n@ObjectType({\n  description: 'A feat representing a special talent or expertise giving unique capabilities.'\n})\n@srdModelOptions('2014-feats')\nexport class Feat {\n  @Field(() => String, { description: 'The unique identifier for this feat (e.g., grappler).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the feat (e.g., Grappler).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => [Prerequisite], { description: 'Prerequisites that must be met to take the feat.' })\n  @prop({ type: () => [Prerequisite] })\n  public prerequisites!: Prerequisite[]\n\n  @Field(() => [String], { description: 'A description of the benefits conferred by the feat.' })\n  @prop({ required: true, index: true, type: () => [String] })\n  public desc!: string[]\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type FeatDocument = DocumentType<Feat>\nconst FeatModel = getModelForClass(Feat)\n\nexport default FeatModel\n"
  },
  {
    "path": "src/models/2014/feature.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, Int, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { Choice } from '@/models/common/choice'\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { Class } from './class'\nimport { Spell } from './spell'\nimport { Subclass } from './subclass'\n\n// Export nested classes\n@ObjectType({ description: 'Prerequisite based on character level' })\nexport class LevelPrerequisite {\n  @Field(() => String, { description: 'Type indicator for this prerequisite.' })\n  @prop({ required: true, index: true, type: () => String })\n  public type!: string\n\n  @Field(() => Int, { description: 'The character level required.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public level!: number\n}\n\n@ObjectType({ description: 'Prerequisite based on having another feature' })\nexport class FeaturePrerequisite {\n  @Field(() => String, { description: 'Type indicator for this prerequisite.' })\n  @prop({ required: true, index: true, type: () => String })\n  public type!: string\n\n  @Field(() => Feature, { description: 'The specific feature required.' })\n  @prop({ required: true, index: true, type: () => String })\n  public feature!: string\n}\n\n@ObjectType({ description: 'Prerequisite based on knowing a specific spell' })\nexport class SpellPrerequisite {\n  @Field(() => String, { description: 'Type indicator for this prerequisite.' })\n  @prop({ required: true, index: true, type: () => String })\n  public type!: string\n\n  @Field(() => Spell, { description: 'The specific spell required.' })\n  @prop({ required: true, index: true, type: () => String })\n  public spell!: string\n}\n\nexport type Prerequisite = LevelPrerequisite | FeaturePrerequisite | SpellPrerequisite\n\n@ObjectType({ description: 'Specific details related to a feature' })\nexport class FeatureSpecific {\n  @prop({ type: () => Choice })\n  public subfeature_options?: Choice\n\n  @prop({ type: () => Choice })\n  public expertise_options?: Choice\n\n  @prop({ type: () => Choice })\n  public terrain_type_options?: Choice\n\n  @prop({ type: () => Choice })\n  public enemy_type_options?: Choice\n\n  @Field(() => [Feature], { nullable: true, description: 'Invocations related to this feature.' })\n  @prop({ type: () => [APIReference] })\n  public invocations?: APIReference[]\n}\n\n@ObjectType({ description: 'Represents a class or subclass feature.' })\n@srdModelOptions('2014-features')\nexport class Feature {\n  @Field(() => Class, { nullable: true, description: 'The class that gains this feature.' })\n  @prop({ type: () => APIReference })\n  public class!: APIReference\n\n  @Field(() => [String], { description: 'Description of the feature.' })\n  @prop({ required: true, index: true, type: () => [String] })\n  public desc!: string[]\n\n  @Field(() => Feature, { nullable: true, description: 'A parent feature, if applicable.' })\n  @prop({ type: () => APIReference })\n  public parent?: APIReference\n\n  @Field(() => String, { description: 'Unique identifier for this feature.' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => Int, { description: 'Level at which the feature is gained.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public level!: number\n\n  @Field(() => String, { description: 'Name of the feature.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  // Handled by FeatureResolver\n  @prop({ type: () => [Object] })\n  public prerequisites?: Prerequisite[]\n\n  @Field(() => String, {\n    nullable: true,\n    description: 'Reference information (e.g., book and page number).'\n  })\n  @prop({ index: true, type: () => String })\n  public reference?: string\n\n  @Field(() => Subclass, {\n    nullable: true,\n    description: 'The subclass that gains this feature, if applicable.'\n  })\n  @prop({ type: () => APIReference })\n  public subclass?: APIReference\n\n  @Field(() => FeatureSpecific, {\n    nullable: true,\n    description: 'Specific details for this feature, if applicable.'\n  })\n  @prop({ type: () => FeatureSpecific })\n  public feature_specific?: FeatureSpecific\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type FeatureDocument = DocumentType<Feature>\nconst FeatureModel = getModelForClass(Feature)\n\nexport default FeatureModel\n"
  },
  {
    "path": "src/models/2014/language.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({ description: 'Represents a language spoken in the D&D world.' })\n@srdModelOptions('2014-languages')\nexport class Language {\n  @Field(() => String, { nullable: true, description: 'A brief description of the language.' })\n  @prop({ index: true, type: () => String })\n  public desc?: string\n\n  @Field(() => String, { description: 'The unique identifier for this language (e.g., common).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the language (e.g., Common).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, {\n    nullable: true,\n    description: 'The script used to write the language (e.g., Common, Elvish).'\n  })\n  @prop({ index: true, type: () => String })\n  public script?: string\n\n  @Field(() => String, { description: 'The type of language (e.g., Standard, Exotic).' })\n  @prop({ required: true, index: true, type: () => String })\n  public type!: string\n\n  @Field(() => [String], { description: 'Typical speakers of the language.' })\n  @prop({ type: () => [String], index: true })\n  public typical_speakers!: string[]\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type LanguageDocument = DocumentType<Language>\nconst LanguageModel = getModelForClass(Language)\n\nexport default LanguageModel\n"
  },
  {
    "path": "src/models/2014/level.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, Float, Int, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { Class } from './class'\nimport { Feature } from './feature'\nimport { Subclass } from './subclass'\n\n// Export nested classes\n@ObjectType({ description: 'Spell slot creation details for Sorcerer levels' })\nexport class ClassSpecificCreatingSpellSlot {\n  @Field(() => Int, { description: 'Cost in sorcery points.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public sorcery_point_cost!: number\n\n  @Field(() => Int, { description: 'Level of the spell slot created.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public spell_slot_level!: number\n}\n\n@ObjectType({ description: 'Martial arts details for Monk levels' })\nexport class ClassSpecificMartialArt {\n  @Field(() => Int, { description: 'Number of dice for martial arts damage.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public dice_count!: number\n\n  @Field(() => Int, { description: 'Value of the dice used (e.g., 4 for d4).' })\n  @prop({ required: true, index: true, type: () => Number })\n  public dice_value!: number\n}\n\n@ObjectType({ description: 'Sneak attack details for Rogue levels' })\nexport class ClassSpecificSneakAttack {\n  @Field(() => Int, { description: 'Number of dice for sneak attack damage.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public dice_count!: number\n\n  @Field(() => Int, { description: 'Value of the dice used (e.g., 6 for d6).' })\n  @prop({ required: true, index: true, type: () => Number })\n  public dice_value!: number\n}\n\n@ObjectType({ description: 'Class-specific features and values gained at a level' })\nexport class ClassSpecific {\n  @Field(() => Int, { nullable: true, description: 'Number of Action Surges available.' })\n  @prop({ index: true, type: () => Number })\n  public action_surges?: number\n\n  @Field(() => Int, {\n    nullable: true,\n    description: 'Maximum spell level recoverable via Arcane Recovery.'\n  })\n  @prop({ index: true, type: () => Number })\n  public arcane_recovery_levels?: number\n\n  @Field(() => Int, { nullable: true, description: 'Range of Paladin auras in feet.' })\n  @prop({ index: true, type: () => Number })\n  public aura_range?: number\n\n  @Field(() => Int, {\n    nullable: true,\n    description: 'Die size for Bardic Inspiration (e.g., 6 for d6).'\n  })\n  @prop({ index: true, type: () => Number })\n  public bardic_inspiration_die?: number\n\n  @Field(() => Int, {\n    nullable: true,\n    description: \"Number of extra damage dice for Barbarian's Brutal Critical.\"\n  })\n  @prop({ index: true, type: () => Number })\n  public brutal_critical_dice?: number\n\n  @Field(() => Int, { nullable: true, description: 'Number of uses for Channel Divinity.' })\n  @prop({ index: true, type: () => Number })\n  public channel_divinity_charges?: number\n\n  @Field(() => [ClassSpecificCreatingSpellSlot], {\n    nullable: true,\n    description: 'Sorcerer spell slot creation options.'\n  })\n  @prop({ type: () => [ClassSpecificCreatingSpellSlot], default: undefined })\n  public creating_spell_slots?: ClassSpecificCreatingSpellSlot[]\n\n  @Field(() => Float, {\n    nullable: true,\n    description: 'Maximum Challenge Rating of undead that can be destroyed by Channel Divinity.'\n  })\n  @prop({ index: true, type: () => Number })\n  public destroy_undead_cr?: number\n\n  @Field(() => Int, { nullable: true, description: 'Number of extra attacks granted.' })\n  @prop({ index: true, type: () => Number })\n  public extra_attacks?: number\n\n  @Field(() => Int, { nullable: true, description: 'Number of favored enemies known by Ranger.' })\n  @prop({ index: true, type: () => Number })\n  public favored_enemies?: number\n\n  @Field(() => Int, { nullable: true, description: 'Number of favored terrains known by Ranger.' })\n  @prop({ index: true, type: () => Number })\n  public favored_terrain?: number\n\n  @Field(() => Int, {\n    nullable: true,\n    description: \"Number of uses for Fighter's Indomitable feature.\"\n  })\n  @prop({ index: true, type: () => Number })\n  public indomitable_uses?: number\n\n  @Field(() => Int, { nullable: true, description: 'Number of Warlock invocations known.' })\n  @prop({ index: true, type: () => Number })\n  public invocations_known?: number\n\n  @Field(() => Int, { nullable: true, description: 'Number of Monk ki points.' })\n  @prop({ index: true, type: () => Number })\n  public ki_points?: number\n\n  @Field(() => Int, {\n    nullable: true,\n    description: \"Maximum level of spells gained via Bard's Magical Secrets (up to level 5).\"\n  })\n  @prop({ index: true, type: () => Number })\n  public magical_secrets_max_5?: number\n\n  @Field(() => Int, {\n    nullable: true,\n    description: \"Maximum level of spells gained via Bard's Magical Secrets (up to level 7).\"\n  })\n  @prop({ index: true, type: () => Number })\n  public magical_secrets_max_7?: number\n\n  @Field(() => Int, {\n    nullable: true,\n    description: \"Maximum level of spells gained via Bard's Magical Secrets (up to level 9).\"\n  })\n  @prop({ index: true, type: () => Number })\n  public magical_secrets_max_9?: number\n\n  @Field(() => ClassSpecificMartialArt, {\n    nullable: true,\n    description: 'Monk martial arts damage progression.'\n  })\n  @prop({ type: () => ClassSpecificMartialArt })\n  public martial_arts?: ClassSpecificMartialArt\n\n  @Field(() => Int, { nullable: true, description: 'Number of Sorcerer metamagic options known.' })\n  @prop({ index: true, type: () => Number })\n  public metamagic_known?: number\n\n  @Field(() => Int, {\n    nullable: true,\n    description: 'Indicates if Warlock gained level 6 Mystic Arcanum (1 = yes).'\n  })\n  @prop({ index: true, type: () => Number })\n  public mystic_arcanum_level_6?: number\n\n  @Field(() => Int, {\n    nullable: true,\n    description: 'Indicates if Warlock gained level 7 Mystic Arcanum (1 = yes).'\n  })\n  @prop({ index: true, type: () => Number })\n  public mystic_arcanum_level_7?: number\n\n  @Field(() => Int, {\n    nullable: true,\n    description: 'Indicates if Warlock gained level 8 Mystic Arcanum (1 = yes).'\n  })\n  @prop({ index: true, type: () => Number })\n  public mystic_arcanum_level_8?: number\n\n  @Field(() => Int, {\n    nullable: true,\n    description: 'Indicates if Warlock gained level 9 Mystic Arcanum (1 = yes).'\n  })\n  @prop({ index: true, type: () => Number })\n  public mystic_arcanum_level_9?: number\n\n  @Field(() => Int, { nullable: true, description: 'Number of Barbarian rages per long rest.' })\n  @prop({ index: true, type: () => Number })\n  public rage_count?: number\n\n  @Field(() => Int, {\n    nullable: true,\n    description: 'Damage bonus added to Barbarian rage attacks.'\n  })\n  @prop({ index: true, type: () => Number })\n  public rage_damage_bonus?: number\n\n  @Field(() => ClassSpecificSneakAttack, {\n    nullable: true,\n    description: 'Rogue sneak attack damage progression.'\n  })\n  @prop({ type: () => ClassSpecificSneakAttack })\n  public sneak_attack?: ClassSpecificSneakAttack\n\n  @Field(() => Int, {\n    nullable: true,\n    description: \"Die size for Bard's Song of Rest (e.g., 6 for d6).\"\n  })\n  @prop({ index: true, type: () => Number })\n  public song_of_rest_die?: number\n\n  @Field(() => Int, { nullable: true, description: 'Number of Sorcerer sorcery points.' })\n  @prop({ index: true, type: () => Number })\n  public sorcery_points?: number\n\n  @Field(() => Int, {\n    nullable: true,\n    description: \"Bonus speed for Monk's Unarmored Movement in feet.\"\n  })\n  @prop({ index: true, type: () => Number })\n  public unarmored_movement?: number\n\n  @Field(() => Boolean, {\n    nullable: true,\n    description: \"Indicates if Druid's Wild Shape allows flying.\"\n  })\n  @prop({ index: true, type: () => Boolean })\n  public wild_shape_fly?: boolean\n\n  @Field(() => Float, {\n    nullable: true,\n    description: \"Maximum Challenge Rating for Druid's Wild Shape form.\"\n  })\n  @prop({ index: true, type: () => Number })\n  public wild_shape_max_cr?: number\n\n  @Field(() => Boolean, {\n    nullable: true,\n    description: \"Indicates if Druid's Wild Shape allows swimming.\"\n  })\n  @prop({ index: true, type: () => Boolean })\n  public wild_shape_swim?: boolean\n}\n\n@ObjectType({ description: 'Spellcasting details for a class at a specific level' })\nexport class LevelSpellcasting {\n  @Field(() => Int, { nullable: true, description: 'Number of cantrips known.' })\n  @prop({ index: true, type: () => Number })\n  public cantrips_known?: number\n\n  @Field(() => Int, { description: 'Number of level 1 spell slots.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public spell_slots_level_1!: number\n\n  @Field(() => Int, { description: 'Number of level 2 spell slots.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public spell_slots_level_2!: number\n\n  @Field(() => Int, { description: 'Number of level 3 spell slots.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public spell_slots_level_3!: number\n\n  @Field(() => Int, { description: 'Number of level 4 spell slots.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public spell_slots_level_4!: number\n\n  @Field(() => Int, { description: 'Number of level 5 spell slots.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public spell_slots_level_5!: number\n\n  @Field(() => Int, { nullable: true, description: 'Number of level 6 spell slots.' })\n  @prop({ index: true, type: () => Number })\n  public spell_slots_level_6?: number\n\n  @Field(() => Int, { nullable: true, description: 'Number of level 7 spell slots.' })\n  @prop({ index: true, type: () => Number })\n  public spell_slots_level_7?: number\n\n  @Field(() => Int, { nullable: true, description: 'Number of level 8 spell slots.' })\n  @prop({ index: true, type: () => Number })\n  public spell_slots_level_8?: number\n\n  @Field(() => Int, { nullable: true, description: 'Number of level 9 spell slots.' })\n  @prop({ index: true, type: () => Number })\n  public spell_slots_level_9?: number\n\n  @Field(() => Int, {\n    nullable: true,\n    description: 'Total number of spells known (for certain classes like Sorcerer).'\n  })\n  @prop({ index: true, type: () => Number })\n  public spells_known?: number\n}\n\n@ObjectType({ description: 'Subclass-specific features and values gained at a level' })\nexport class SubclassSpecific {\n  @Field(() => Int, {\n    nullable: true,\n    description: \"Maximum level of spells gained via Bard's Additional Magical Secrets.\"\n  })\n  @prop({ index: true, type: () => Number })\n  public additional_magical_secrets_max_lvl?: number\n\n  @Field(() => Int, {\n    nullable: true,\n    description: 'Range of subclass-specific auras (e.g., Paladin) in feet.'\n  })\n  @prop({ index: true, type: () => Number })\n  public aura_range?: number\n}\n\n@ObjectType({\n  description: 'Represents the features and abilities gained at a specific class level'\n})\n@srdModelOptions('2014-levels')\nexport class Level {\n  @Field(() => Int, {\n    nullable: true,\n    description: 'Number of ability score bonuses gained at this level'\n  })\n  @prop({ index: true, type: () => Number })\n  public ability_score_bonuses?: number\n\n  @Field(() => Class, { nullable: true, description: 'The class this level belongs to.' })\n  @prop({ type: () => APIReference })\n  public class!: APIReference\n\n  @Field(() => ClassSpecific, {\n    nullable: true,\n    description: 'Class-specific details for this level.'\n  })\n  @prop({ type: () => ClassSpecific })\n  public class_specific?: ClassSpecific\n\n  @Field(() => [Feature], { nullable: true, description: 'Features gained at this level.' })\n  @prop({ type: () => [APIReference] })\n  public features?: APIReference[]\n\n  @Field(() => String, {\n    description: 'Unique identifier for this level (e.g., barbarian-1, rogue-20)'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => Int, { description: 'The class level (1-20)' })\n  @prop({ required: true, index: true, type: () => Number })\n  public level!: number\n\n  @Field(() => Int, { nullable: true, description: 'Proficiency bonus gained at this level' })\n  @prop({ index: true, type: () => Number })\n  public prof_bonus?: number\n\n  @Field(() => LevelSpellcasting, {\n    nullable: true,\n    description: 'Spellcasting progression details for this level.'\n  })\n  @prop({ type: () => LevelSpellcasting })\n  public spellcasting?: LevelSpellcasting\n\n  @Field(() => Subclass, {\n    nullable: true,\n    description: 'The subclass this level relates to, if applicable.'\n  })\n  @prop({ type: () => APIReference })\n  public subclass?: APIReference\n\n  @Field(() => SubclassSpecific, {\n    nullable: true,\n    description: 'Subclass-specific details for this level.'\n  })\n  @prop({ type: () => SubclassSpecific })\n  public subclass_specific?: SubclassSpecific\n\n  // url field is not exposed via GraphQL\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type LevelDocument = DocumentType<Level>\nconst LevelModel = getModelForClass(Level)\n\nexport default LevelModel\n"
  },
  {
    "path": "src/models/2014/magicItem.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { EquipmentCategory } from './equipmentCategory'\n\n@ObjectType({ description: 'Rarity level of a magic item.' })\nexport class Rarity {\n  @Field(() => String, {\n    description: 'The name of the rarity level (e.g., Common, Uncommon, Rare).'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n}\n\n@ObjectType({ description: 'An item imbued with magical properties.' })\n@srdModelOptions('2014-magic-items')\nexport class MagicItem {\n  @Field(() => [String], {\n    description: 'A description of the magic item, including its effects and usage.'\n  })\n  @prop({ type: () => [String], index: true })\n  public desc!: string[]\n\n  @Field(() => EquipmentCategory, {\n    description: 'The category of equipment this magic item belongs to.'\n  })\n  @prop({ type: () => APIReference, index: true })\n  public equipment_category!: APIReference\n\n  @Field(() => String, {\n    nullable: true,\n    description: 'URL of an image for the magic item, if available.'\n  })\n  @prop({ type: () => String, index: true })\n  public image?: string\n\n  @Field(() => String, {\n    description: 'The unique identifier for this magic item (e.g., adamantite-armor).'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the magic item (e.g., Adamantite Armor).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => Rarity, { description: 'The rarity of the magic item.' })\n  @prop({ required: true, index: true, type: () => Rarity })\n  public rarity!: Rarity\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => [MagicItem], {\n    nullable: true,\n    description: 'Other magic items that are variants of this item.'\n  })\n  @prop({ type: () => [APIReference], index: true })\n  public variants!: APIReference[]\n\n  @Field(() => Boolean, {\n    description: 'Indicates if this magic item is a variant of another item.'\n  })\n  @prop({ required: true, index: true, type: () => Boolean })\n  public variant!: boolean\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type MagicItemDocument = DocumentType<MagicItem>\nconst MagicItemModel = getModelForClass(MagicItem)\n\nexport default MagicItemModel\n"
  },
  {
    "path": "src/models/2014/magicSchool.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({\n  description: 'A school of magic, representing a particular tradition like Evocation or Illusion.'\n})\n@srdModelOptions('2014-magic-schools')\nexport class MagicSchool {\n  @Field(() => String, { description: 'A brief description of the school of magic.' })\n  @prop({ type: () => String, index: true })\n  public desc!: string\n\n  @Field(() => String, { description: 'The unique identifier for this school (e.g., evocation).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the school (e.g., Evocation).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type MagicSchoolDocument = DocumentType<MagicSchool>\nconst MagicSchoolModel = getModelForClass(MagicSchool)\n\nexport default MagicSchoolModel\n"
  },
  {
    "path": "src/models/2014/monster.ts",
    "content": "import { getModelForClass, modelOptions, prop, Severity } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, Float, Int, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { Choice } from '@/models/common/choice'\nimport { Damage } from '@/models/common/damage'\nimport { DifficultyClass } from '@/models/common/difficultyClass'\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { AbilityScore } from './abilityScore'\nimport { Condition } from './condition'\nimport { Proficiency } from './proficiency'\nimport { Spell } from './spell'\n\n// Export all nested classes/types\n@ObjectType({ description: 'Option within a monster action' })\nexport class ActionOption {\n  @Field(() => String, { description: 'The name of the action.' })\n  @prop({ required: true, index: true, type: () => String })\n  public action_name!: string\n\n  @Field(() => String, { description: 'Number of times the action can be used.' })\n  @prop({ required: true, index: true, type: () => String })\n  public count!: number | string\n\n  @Field(() => String, { description: 'The type of action.' })\n  @prop({ required: true, index: true, type: () => String })\n  public type!: 'melee' | 'ranged' | 'ability' | 'magic'\n}\n\n@ObjectType({ description: 'Usage details for a monster action or ability' })\nexport class ActionUsage {\n  @Field(() => String, { description: 'The type of action usage.' })\n  @prop({ required: true, index: true, type: () => String })\n  public type!: string\n\n  @Field(() => String, { nullable: true, description: 'The dice roll for the action usage.' })\n  @prop({ index: true, type: () => String })\n  public dice?: string\n\n  @Field(() => Int, { nullable: true, description: 'The minimum value for the action usage.' })\n  @prop({ index: true, type: () => Number })\n  public min_value?: number\n}\n\n@ObjectType({ description: 'An action a monster can perform' })\nexport class MonsterAction {\n  @Field(() => String, { description: 'The name of the action.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, { description: 'The description of the action.' })\n  @prop({ required: true, index: true, type: () => String })\n  public desc!: string\n\n  @Field(() => Int, { nullable: true, description: 'The attack bonus for the action.' })\n  @prop({ index: true, type: () => Number })\n  public attack_bonus?: number\n\n  // Handled by MonsterActionResolver\n  @prop({ type: () => [Object] })\n  public damage?: (Damage | Choice)[]\n\n  @Field(() => DifficultyClass, {\n    nullable: true,\n    description: 'The difficulty class for the action.'\n  })\n  @prop({ type: () => DifficultyClass })\n  public dc?: DifficultyClass\n\n  // Handled by MonsterActionResolver\n  @prop({ type: () => Choice })\n  public options?: Choice\n\n  @Field(() => ActionUsage, { nullable: true, description: 'The usage for the action.' })\n  @prop({ type: () => ActionUsage })\n  public usage?: ActionUsage\n\n  @Field(() => String, { nullable: true, description: 'The type of multiattack for the action.' })\n  @prop({ required: true, index: true, type: () => String })\n  public multiattack_type?: 'actions' | 'action_options'\n\n  @Field(() => [ActionOption], { nullable: true, description: 'The actions for the action.' })\n  @prop({ type: () => [ActionOption] })\n  public actions?: ActionOption[]\n\n  // Handled by MonsterActionResolver\n  @prop({ type: () => Choice })\n  public action_options?: Choice\n}\n\n@ObjectType({ description: 'Monster Armor Class component: Dexterity based' })\nexport class ArmorClassDex {\n  @Field(() => String, { description: \"Type of AC component: 'dex'\" })\n  @prop({ required: true, index: true, type: () => String })\n  public type!: 'dex'\n\n  @Field(() => Int, { description: 'AC value from dexterity.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public value!: number\n\n  @Field(() => String, {\n    nullable: true,\n    description: 'Optional description for this AC component.'\n  })\n  @prop({ index: true, type: () => String })\n  public desc?: string\n}\n\n@ObjectType({ description: 'Monster Armor Class component: Natural armor' })\nexport class ArmorClassNatural {\n  @Field(() => String, { description: \"Type of AC component: 'natural'\" })\n  @prop({ required: true, index: true, type: () => String })\n  public type!: 'natural'\n\n  @Field(() => Int, { description: 'AC value from natural armor.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public value!: number\n\n  @Field(() => String, {\n    nullable: true,\n    description: 'Optional description for this AC component.'\n  })\n  @prop({ index: true, type: () => String })\n  public desc?: string\n}\n\n@ObjectType({ description: 'Monster Armor Class component: Armor worn' })\nexport class ArmorClassArmor {\n  @Field(() => String, { description: \"Type of AC component: 'armor'\" })\n  @prop({ required: true, index: true, type: () => String })\n  public type!: 'armor'\n\n  @Field(() => Int, { description: 'AC value from worn armor.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public value!: number\n\n  // Handled by MonsterArmorClassResolver\n  @prop({ type: () => [APIReference] })\n  public armor?: APIReference[]\n\n  @Field(() => String, {\n    nullable: true,\n    description: 'Optional description for this AC component.'\n  })\n  @prop({ index: true, type: () => String })\n  public desc?: string\n}\n\n@ObjectType({ description: 'Monster Armor Class component: Spell effect' })\nexport class ArmorClassSpell {\n  @Field(() => String, { description: \"Type of AC component: 'spell'\" })\n  @prop({ required: true, index: true, type: () => String })\n  public type!: 'spell'\n\n  @Field(() => Int, { description: 'AC value from spell effect.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public value!: number\n\n  @Field(() => Spell, {\n    description: 'The spell providing the AC bonus. Resolved via resolver.'\n  })\n  @prop({ type: () => APIReference })\n  public spell!: APIReference\n\n  @Field(() => String, {\n    nullable: true,\n    description: 'Optional description for this AC component.'\n  })\n  @prop({ index: true, type: () => String })\n  public desc?: string\n}\n\n@ObjectType({ description: 'Monster Armor Class component: Condition effect' })\nexport class ArmorClassCondition {\n  @Field(() => String, { description: \"Type of AC component: 'condition'\" })\n  @prop({ required: true, index: true, type: () => String })\n  public type!: 'condition'\n\n  @Field(() => Int, { description: 'AC value from condition effect.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public value!: number\n\n  @Field(() => Condition, {\n    description: 'The condition providing the AC bonus. Resolved via resolver.'\n  })\n  @prop({ type: () => APIReference })\n  public condition!: APIReference\n\n  @Field(() => String, {\n    nullable: true,\n    description: 'Optional description for this AC component.'\n  })\n  @prop({ index: true, type: () => String })\n  public desc?: string\n}\n\n@ObjectType({ description: 'A legendary action a monster can perform' })\nexport class LegendaryAction {\n  @Field(() => String, { description: 'The name of the legendary action.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, { description: 'The description of the legendary action.' })\n  @prop({ required: true, index: true, type: () => String })\n  public desc!: string\n\n  @Field(() => Int, { nullable: true, description: 'The attack bonus for the legendary action.' })\n  @prop({ index: true, type: () => Number })\n  public attack_bonus?: number\n\n  @Field(() => [Damage], { nullable: true, description: 'The damage for the legendary action.' })\n  @prop({ type: () => [Damage] })\n  public damage?: Damage[]\n\n  @Field(() => DifficultyClass, {\n    nullable: true,\n    description: 'The difficulty class for the legendary action.'\n  })\n  @prop({ type: () => DifficultyClass })\n  public dc?: DifficultyClass\n}\n\n@ObjectType({ description: \"A monster's specific proficiency and its bonus value.\" })\nexport class MonsterProficiency {\n  @Field(() => Proficiency, {\n    description: 'The specific proficiency (e.g., Saving Throw: STR, Skill: Athletics).'\n  })\n  @prop({ type: () => APIReference })\n  public proficiency!: APIReference\n\n  @Field(() => Int, { description: 'The proficiency bonus value for this monster.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public value!: number\n}\n\n@ObjectType({ description: 'A reaction a monster can perform' })\nexport class Reaction {\n  @Field(() => String, { description: 'The name of the reaction.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, { description: 'The description of the reaction.' })\n  @prop({ required: true, index: true, type: () => String })\n  public desc!: string\n\n  @Field(() => DifficultyClass, {\n    nullable: true,\n    description: 'The difficulty class for the reaction.'\n  })\n  @prop({ type: () => DifficultyClass })\n  public dc?: DifficultyClass\n}\n\n@ObjectType({ description: 'Monster senses details' })\nexport class Sense {\n  @Field(() => String, { nullable: true })\n  @prop({ index: true, type: () => String })\n  public blindsight?: string\n\n  @Field(() => String, { nullable: true })\n  @prop({ index: true, type: () => String })\n  public darkvision?: string\n\n  @Field(() => Int)\n  @prop({ required: true, index: true, type: () => Number })\n  public passive_perception!: number\n\n  @Field(() => String, { nullable: true })\n  @prop({ index: true, type: () => String })\n  public tremorsense?: string\n\n  @Field(() => String, { nullable: true })\n  @prop({ index: true, type: () => String })\n  public truesight?: string\n}\n\n@ObjectType({ description: 'Usage details for a special ability' })\nexport class SpecialAbilityUsage {\n  @Field(() => String, { description: 'The type of usage for the special ability.' })\n  @prop({ required: true, index: true, type: () => String })\n  public type!: string\n\n  @Field(() => Int, {\n    nullable: true,\n    description: 'The number of times the special ability can be used.'\n  })\n  @prop({ index: true, type: () => Number })\n  public times?: number\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'The types of rest the special ability can be used on.'\n  })\n  @prop({ type: () => [String] })\n  public rest_types?: string[]\n}\n\n@ObjectType({ description: \"A spell within a monster's special ability spellcasting\" })\nexport class SpecialAbilitySpell {\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => Int, { description: 'The level of the spell.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public level!: number\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { nullable: true, description: 'The notes for the spell.' })\n  @prop({ index: true, type: () => String })\n  public notes?: string\n\n  @Field(() => SpecialAbilityUsage, { nullable: true, description: 'The usage for the spell.' })\n  @prop({ type: () => SpecialAbilityUsage })\n  public usage?: SpecialAbilityUsage\n}\n\n@ObjectType({ description: 'Spellcasting details for a monster special ability' })\n@modelOptions({ options: { allowMixed: Severity.ALLOW } })\nexport class SpecialAbilitySpellcasting {\n  @Field(() => Int, { nullable: true, description: 'The level of the spellcasting.' })\n  @prop({ index: true, type: () => Number })\n  public level?: number\n\n  @Field(() => AbilityScore, { description: 'The ability for the spellcasting.' })\n  @prop({ type: () => APIReference })\n  public ability!: APIReference\n\n  @Field(() => Int, { nullable: true, description: 'The difficulty class for the spellcasting.' })\n  @prop({ index: true, type: () => Number })\n  public dc?: number\n\n  @Field(() => Int, { nullable: true, description: 'The modifier for the spellcasting.' })\n  @prop({ index: true, type: () => Number })\n  public modifier?: number\n\n  @Field(() => [String], { description: 'The components required for the spellcasting.' })\n  @prop({ type: () => [String] })\n  public components_required!: string[]\n\n  @Field(() => String, { nullable: true, description: 'The school of the spellcasting.' })\n  @prop({ index: true, type: () => String })\n  public school?: string\n\n  // Handled by MonsterSpellcastingResolver\n  @prop({ type: () => Object, default: undefined })\n  public slots?: Record<string, number>\n\n  @Field(() => [SpecialAbilitySpell], { description: 'The spells for the spellcasting.' })\n  @prop({ type: () => [SpecialAbilitySpell] })\n  public spells!: SpecialAbilitySpell[]\n}\n\n@ObjectType({ description: 'A special ability of the monster' })\nexport class SpecialAbility {\n  @Field(() => String, { description: 'The name of the special ability.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, { description: 'The description of the special ability.' })\n  @prop({ required: true, index: true, type: () => String })\n  public desc!: string\n\n  @Field(() => Int, { nullable: true, description: 'The attack bonus for the special ability.' })\n  @prop({ index: true, type: () => Number })\n  public attack_bonus?: number\n\n  @Field(() => [Damage], { nullable: true, description: 'The damage for the special ability.' })\n  @prop({ type: () => [Damage] })\n  public damage?: Damage[]\n\n  @Field(() => DifficultyClass, {\n    nullable: true,\n    description: 'The difficulty class for the special ability.'\n  })\n  @prop({ type: () => DifficultyClass })\n  public dc?: DifficultyClass\n\n  @Field(() => SpecialAbilitySpellcasting, {\n    nullable: true,\n    description: 'The spellcasting for the special ability.'\n  })\n  @prop({ type: () => SpecialAbilitySpellcasting })\n  public spellcasting?: SpecialAbilitySpellcasting\n\n  @Field(() => SpecialAbilityUsage, {\n    nullable: true,\n    description: 'The usage for the special ability.'\n  })\n  @prop({ type: () => SpecialAbilityUsage })\n  public usage?: SpecialAbilityUsage\n}\n\n@ObjectType({ description: 'Monster movement speeds' })\nexport class MonsterSpeed {\n  @Field(() => String, { nullable: true })\n  @prop({ index: true, type: () => String })\n  public burrow?: string\n\n  @Field(() => String, { nullable: true })\n  @prop({ index: true, type: () => String })\n  public climb?: string\n\n  @Field(() => String, { nullable: true })\n  @prop({ index: true, type: () => String })\n  public fly?: string\n\n  @Field(() => Boolean, { nullable: true })\n  @prop({ index: true, type: () => Boolean })\n  public hover?: boolean\n\n  @Field(() => String, { nullable: true })\n  @prop({ index: true, type: () => String })\n  public swim?: string\n\n  @Field(() => String, { nullable: true })\n  @prop({ index: true, type: () => String })\n  public walk?: string\n}\n\n@ObjectType({ description: 'A D&D monster.' })\n@srdModelOptions('2014-monsters')\nexport class Monster {\n  @Field(() => [MonsterAction], { nullable: true, description: 'The actions for the monster.' })\n  @prop({ type: () => [MonsterAction] })\n  public actions?: MonsterAction[]\n\n  @Field(() => String)\n  @prop({ required: true, index: true, type: () => String })\n  public alignment!: string\n\n  // Handled by MonsterArmorClassResolver\n  @prop({\n    type: () =>\n      Array<\n        ArmorClassDex | ArmorClassNatural | ArmorClassArmor | ArmorClassSpell | ArmorClassCondition\n      >,\n    required: true\n  })\n  public armor_class!: Array<\n    ArmorClassDex | ArmorClassNatural | ArmorClassArmor | ArmorClassSpell | ArmorClassCondition\n  >\n\n  @Field(() => Float)\n  @prop({ required: true, index: true, type: () => Number })\n  public challenge_rating!: number\n\n  @Field(() => Int)\n  @prop({ required: true, index: true, type: () => Number })\n  public charisma!: number\n\n  @Field(() => [Condition], { nullable: true, description: 'Conditions the monster is immune to.' })\n  @prop({ type: () => [APIReference] })\n  public condition_immunities!: APIReference[]\n\n  @Field(() => Int)\n  @prop({ required: true, index: true, type: () => Number })\n  public constitution!: number\n\n  @Field(() => [String])\n  @prop({ type: () => [String] })\n  public damage_immunities!: string[]\n\n  @Field(() => [String])\n  @prop({ type: () => [String] })\n  public damage_resistances!: string[]\n\n  @Field(() => [String])\n  @prop({ type: () => [String] })\n  public damage_vulnerabilities!: string[]\n\n  @Field(() => Int)\n  @prop({ required: true, index: true, type: () => Number })\n  public dexterity!: number\n\n  @Field(() => [Monster], { nullable: true, description: 'Other forms the monster can assume.' })\n  @prop({ type: () => [APIReference] })\n  public forms?: APIReference[]\n\n  @Field(() => String)\n  @prop({ required: true, index: true, type: () => String })\n  public hit_dice!: string\n\n  @Field(() => Int)\n  @prop({ required: true, index: true, type: () => Number })\n  public hit_points!: number\n\n  @Field(() => String)\n  @prop({ required: true, index: true, type: () => String })\n  public hit_points_roll!: string\n\n  @Field(() => String, { nullable: true })\n  @prop({ index: true, type: () => String })\n  public image?: string\n\n  @Field(() => String)\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => Int)\n  @prop({ required: true, index: true, type: () => Number })\n  public intelligence!: number\n\n  @Field(() => String)\n  @prop({ required: true, index: true, type: () => String })\n  public languages!: string\n\n  @Field(() => [LegendaryAction], {\n    nullable: true,\n    description: 'The legendary actions for the monster.'\n  })\n  @prop({ type: () => [LegendaryAction] })\n  public legendary_actions?: LegendaryAction[]\n\n  @Field(() => String)\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => [MonsterProficiency], {\n    nullable: true,\n    description: 'The proficiencies for the monster.'\n  })\n  @prop({ type: () => [MonsterProficiency] })\n  public proficiencies!: MonsterProficiency[]\n\n  @Field(() => [Reaction], { nullable: true, description: 'The reactions for the monster.' })\n  @prop({ type: () => [Reaction] })\n  public reactions?: Reaction[]\n\n  @Field(() => Sense)\n  @prop({ type: () => Sense })\n  public senses!: Sense\n\n  @Field(() => String)\n  @prop({ required: true, index: true, type: () => String })\n  public size!: string\n\n  @Field(() => [SpecialAbility], {\n    nullable: true,\n    description: 'The special abilities for the monster.'\n  })\n  @prop({ type: () => [SpecialAbility] })\n  public special_abilities?: SpecialAbility[]\n\n  @Field(() => MonsterSpeed)\n  @prop({ type: () => MonsterSpeed })\n  public speed!: MonsterSpeed\n\n  @Field(() => Int)\n  @prop({ required: true, index: true, type: () => Number })\n  public strength!: number\n\n  @Field(() => String, { nullable: true })\n  @prop({ index: true, type: () => String })\n  public subtype?: string\n\n  @Field(() => String)\n  @prop({ required: true, index: true, type: () => String })\n  public type!: string\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => Int)\n  @prop({ required: true, index: true, type: () => Number })\n  public wisdom!: number\n\n  @Field(() => Int)\n  @prop({ required: true, index: true, type: () => Number })\n  public xp!: number\n\n  @Field(() => String)\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type MonsterDocument = DocumentType<Monster>\nconst MonsterModel = getModelForClass(Monster)\n\nexport default MonsterModel\n"
  },
  {
    "path": "src/models/2014/proficiency.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { Class } from './class'\nimport { Race } from './race'\n\n@ObjectType({\n  description: 'Represents a skill, tool, weapon, armor, or saving throw proficiency.'\n})\n@srdModelOptions('2014-proficiencies')\nexport class Proficiency {\n  @Field(() => [Class], { nullable: true, description: 'Classes that grant this proficiency.' })\n  @prop({ type: () => [APIReference] })\n  public classes?: APIReference[]\n\n  @Field(() => String, { description: 'Unique identifier for this proficiency.' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'Name of the proficiency.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => [Race], { nullable: true, description: 'Races that grant this proficiency.' })\n  @prop({ type: () => [APIReference] })\n  public races?: APIReference[]\n\n  @prop({ type: () => APIReference })\n  public reference!: APIReference\n\n  @Field(() => String, {\n    description: 'Category of proficiency (e.g., Armor, Weapons, Saving Throws, Skills).'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public type!: string\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type ProficiencyDocument = DocumentType<Proficiency>\nconst ProficiencyModel = getModelForClass(Proficiency)\n\nexport default ProficiencyModel\n"
  },
  {
    "path": "src/models/2014/race.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, Int, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { Choice } from '@/models/common/choice'\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { AbilityScore } from './abilityScore'\nimport { Language } from './language'\nimport { Subrace } from './subrace'\nimport { Trait } from './trait'\n\n@ObjectType({ description: 'Ability score bonus provided by a race' })\nexport class RaceAbilityBonus {\n  @Field(() => AbilityScore, {\n    nullable: true,\n    description: 'The ability score that receives the bonus.'\n  })\n  @prop({ type: () => APIReference, required: true })\n  public ability_score!: APIReference\n\n  @Field(() => Int, { description: 'The bonus value for the ability score' })\n  @prop({ required: true, index: true, type: () => Number })\n  public bonus!: number\n}\n\n@ObjectType({ description: 'Represents a playable race in D&D' })\n@srdModelOptions('2014-races')\nexport class Race {\n  @Field(() => String, { description: 'The index of the race.' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  // Handled by RaceResolver\n  @prop({ type: () => Choice, required: false, index: true })\n  public ability_bonus_options?: Choice\n\n  @Field(() => [RaceAbilityBonus], { description: 'Ability score bonuses granted by this race.' })\n  @prop({ type: () => [RaceAbilityBonus], required: true })\n  public ability_bonuses!: RaceAbilityBonus[]\n\n  @Field(() => String, { description: 'Typical age range and lifespan for the race' })\n  @prop({ required: true, index: true, type: () => String })\n  public age!: string\n\n  @Field(() => String, { description: 'Typical alignment tendencies for the race' })\n  @prop({ required: true, index: true, type: () => String })\n  public alignment!: string\n\n  @Field(() => String, { description: 'Description of languages typically spoken by the race' })\n  @prop({ required: true, index: true, type: () => String })\n  public language_desc!: string\n\n  // Handled by RaceResolver\n  @prop({ type: () => Choice })\n  public language_options?: Choice\n\n  @Field(() => [Language], {\n    nullable: true,\n    description: 'Languages typically spoken by this race.'\n  })\n  @prop({ type: () => [APIReference], required: true })\n  public languages!: APIReference[]\n\n  @Field(() => String, { description: 'The name of the race.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, { description: 'Size category (e.g., Medium, Small)' })\n  @prop({ required: true, index: true, type: () => String })\n  public size!: string\n\n  @Field(() => String, { description: \"Description of the race's size\" })\n  @prop({ required: true, index: true, type: () => String })\n  public size_description!: string\n\n  @Field(() => Int, { description: 'Base walking speed in feet' })\n  @prop({ required: true, index: true, type: () => Number })\n  public speed!: number\n\n  @Field(() => [Subrace], { nullable: true, description: 'Subraces available for this race.' })\n  @prop({ type: () => [APIReference] })\n  public subraces?: APIReference[]\n\n  @Field(() => [Trait], { nullable: true, description: 'Traits common to this race.' })\n  @prop({ type: () => [APIReference] })\n  public traits?: APIReference[]\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type RaceDocument = DocumentType<Race>\nconst RaceModel = getModelForClass(Race)\n\nexport default RaceModel\n"
  },
  {
    "path": "src/models/2014/rule.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { RuleSection } from './ruleSection'\n\n@ObjectType({ description: 'A specific rule from the SRD.' })\n@srdModelOptions('2014-rules')\nexport class Rule {\n  @Field(() => String, { description: 'A description of the rule.' })\n  @prop({ required: true, index: true, type: () => String })\n  public desc!: string\n\n  @Field(() => String, { description: 'The unique identifier for this rule (e.g., adventuring).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the rule (e.g., Adventuring).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => [RuleSection], {\n    description: 'Subsections clarifying or detailing this rule.'\n  })\n  @prop({ type: () => [APIReference], index: true })\n  public subsections!: APIReference[]\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type RuleDocument = DocumentType<Rule>\nconst RuleModel = getModelForClass(Rule)\n\nexport default RuleModel\n"
  },
  {
    "path": "src/models/2014/ruleSection.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({ description: 'Represents a named section of the SRD rules document.' })\n@srdModelOptions('2014-rule-sections')\nexport class RuleSection {\n  @Field(() => String, { description: 'A description of the rule section.' })\n  @prop({ required: true, index: true, type: () => String })\n  public desc!: string\n\n  @Field(() => String, {\n    description: 'The unique identifier for this rule section (e.g., ability-checks).'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the rule section (e.g., Ability Checks).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type RuleSectionDocument = DocumentType<RuleSection>\nconst RuleSectionModel = getModelForClass(RuleSection)\n\nexport default RuleSectionModel\n"
  },
  {
    "path": "src/models/2014/skill.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { AbilityScore } from './abilityScore'\n\n@ObjectType({\n  description: 'A skill representing proficiency in a specific task (e.g., Athletics, Stealth).'\n})\n@srdModelOptions('2014-skills')\nexport class Skill {\n  @Field(() => AbilityScore, { description: 'The ability score associated with this skill.' })\n  @prop({ type: () => APIReference, required: true })\n  public ability_score!: APIReference\n\n  @Field(() => [String], { description: 'A description of the skill.' })\n  @prop({ required: true, index: true, type: () => [String] })\n  public desc!: string[]\n\n  @Field(() => String, { description: 'The unique identifier for this skill (e.g., athletics).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the skill (e.g., Athletics).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  // url is intentionally not decorated with @Field\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type SkillDocument = DocumentType<Skill>\nconst SkillModel = getModelForClass(Skill)\n\nexport default SkillModel\n"
  },
  {
    "path": "src/models/2014/spell.ts",
    "content": "import { getModelForClass, modelOptions, prop, Severity } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, Int, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { AreaOfEffect } from '@/models/common/areaOfEffect'\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { AbilityScore } from './abilityScore'\nimport { Class } from './class'\nimport { DamageType } from './damageType'\nimport { MagicSchool } from './magicSchool'\nimport { Subclass } from './subclass'\n\n@ObjectType({ description: 'Details about spell damage' })\n@modelOptions({ options: { allowMixed: Severity.ALLOW } })\nexport class SpellDamage {\n  @Field(() => DamageType, { nullable: true, description: 'Type of damage dealt.' })\n  @prop({ type: () => APIReference })\n  public damage_type?: APIReference\n\n  // Handled by SpellDamageResolver\n  @prop({ mapProp: true, type: () => Object, default: undefined })\n  public damage_at_slot_level?: Record<number, string>\n\n  // Handled by SpellDamageResolver\n  @prop({ mapProp: true, type: () => Object, default: undefined })\n  public damage_at_character_level?: Record<number, string>\n}\n\n@ObjectType({ description: \"Details about a spell's saving throw\" })\nexport class SpellDC {\n  @Field(() => AbilityScore, { description: 'The ability score used for the saving throw.' })\n  @prop({ type: () => APIReference, required: true })\n  public dc_type!: APIReference\n\n  @Field(() => String, { description: \"The result of a successful save (e.g., 'half', 'none').\" })\n  @prop({ required: true, index: true, type: () => String })\n  public dc_success!: string\n\n  @Field(() => String, {\n    nullable: true,\n    description: 'Additional description for the saving throw.'\n  })\n  @prop({ index: true, type: () => String })\n  public desc?: string\n}\n\n@ObjectType({ description: 'Represents a spell in D&D' })\n@srdModelOptions('2014-spells')\nexport class Spell {\n  @Field(() => AreaOfEffect, {\n    nullable: true,\n    description: 'Area of effect details, if applicable.'\n  })\n  @prop({ type: () => AreaOfEffect })\n  public area_of_effect?: AreaOfEffect\n\n  @Field(() => String, {\n    nullable: true,\n    description: 'Type of attack associated with the spell (e.g., Melee, Ranged)'\n  })\n  @prop({ index: true, type: () => String })\n  public attack_type?: string\n\n  @Field(() => String, { description: 'Time required to cast the spell' })\n  @prop({ required: true, index: true, type: () => String })\n  public casting_time!: string\n\n  @Field(() => [Class], { nullable: true, description: 'Classes that can cast this spell.' })\n  @prop({ type: () => [APIReference], required: true })\n  public classes!: APIReference[]\n\n  @Field(() => [String], { description: 'Components required for the spell (V, S, M)' })\n  @prop({ type: () => [String], required: true })\n  public components!: string[]\n\n  @Field(() => Boolean, { description: 'Indicates if the spell requires concentration' })\n  @prop({ index: true, type: () => Boolean })\n  public concentration!: boolean\n\n  @Field(() => SpellDamage, { nullable: true, description: 'Damage details, if applicable.' })\n  @prop({ type: () => SpellDamage })\n  public damage?: SpellDamage\n\n  @Field(() => SpellDC, { nullable: true, description: 'Saving throw details, if applicable.' })\n  @prop({ type: () => SpellDC })\n  public dc?: SpellDC\n\n  @Field(() => [String], { description: \"Description of the spell's effects\" })\n  @prop({ required: true, index: true, type: () => [String] })\n  public desc!: string[]\n\n  @Field(() => String, { description: 'Duration of the spell' })\n  @prop({ required: true, index: true, type: () => String })\n  public duration!: string\n\n  // Handled by SpellResolver\n  @prop({ type: () => Object })\n  public heal_at_slot_level?: Record<number, string>\n\n  @Field(() => [String], {\n    nullable: true,\n    description: 'Description of effects when cast at higher levels'\n  })\n  @prop({ type: () => [String] })\n  public higher_level?: string[]\n\n  @Field(() => String, { description: 'Unique identifier for this spell' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => Int, { description: 'Level of the spell (0 for cantrips)' })\n  @prop({ required: true, index: true, type: () => Number })\n  public level!: number\n\n  @Field(() => String, { nullable: true, description: 'Material components required, if any' })\n  @prop({ index: true, type: () => String })\n  public material?: string\n\n  @Field(() => String, { description: 'Name of the spell' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, { description: 'Range of the spell' })\n  @prop({ required: true, index: true, type: () => String })\n  public range!: string\n\n  @Field(() => Boolean, { description: 'Indicates if the spell can be cast as a ritual' })\n  @prop({ required: true, index: true, type: () => Boolean })\n  public ritual!: boolean\n\n  @Field(() => MagicSchool, {\n    nullable: true,\n    description: 'The school of magic this spell belongs to.'\n  })\n  @prop({ type: () => APIReference, required: true })\n  public school!: APIReference\n\n  @Field(() => [Subclass], { nullable: true, description: 'Subclasses that can cast this spell.' })\n  @prop({ type: () => [APIReference], required: true })\n  public subclasses?: APIReference[]\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type SpellDocument = DocumentType<Spell>\nconst SpellModel = getModelForClass(Spell)\n\nexport default SpellModel\n"
  },
  {
    "path": "src/models/2014/subclass.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { Class } from './class'\nimport { Level } from './level'\nimport { Spell } from './spell'\n\n@ObjectType({ description: 'Prerequisite for a subclass spell' })\nexport class Prerequisite {\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @prop({ required: true, type: () => String })\n  public name!: string\n\n  @prop({ required: true, type: () => String })\n  public type!: string\n\n  @prop({ required: true, type: () => String })\n  public url!: string\n}\n\n@ObjectType({ description: 'Spell gained by a subclass' })\nexport class SubclassSpell {\n  // Handled by SubclassSpellResolver\n  @prop({ type: () => [Prerequisite], required: true })\n  public prerequisites!: Prerequisite[]\n\n  @Field(() => Spell, { description: 'The spell gained.' })\n  @prop({ type: () => APIReference, required: true })\n  public spell!: APIReference\n}\n\n@ObjectType({\n  description: 'Represents a subclass (e.g., Path of the Berserker, School of Evocation)'\n})\n@srdModelOptions('2014-subclasses')\nexport class Subclass {\n  @Field(() => Class, { nullable: true, description: 'The parent class for this subclass.' })\n  @prop({ type: () => APIReference, required: true })\n  public class!: APIReference\n\n  @Field(() => [String], { description: 'Description of the subclass' })\n  @prop({ required: true, index: true, type: () => [String] })\n  public desc!: string[]\n\n  @Field(() => String, { description: 'Unique identifier for the subclass' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'Name of the subclass' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => [SubclassSpell], {\n    nullable: true,\n    description: 'Spells specific to this subclass.'\n  })\n  @prop({ type: () => [SubclassSpell] })\n  public spells?: SubclassSpell[]\n\n  @Field(() => String, { description: 'Flavor text describing the subclass' })\n  @prop({ required: true, index: true, type: () => String })\n  public subclass_flavor!: string\n\n  @Field(() => [Level], {\n    nullable: true,\n    description: 'Features and abilities gained by level for this subclass.'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public subclass_levels!: string\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type SubclassDocument = DocumentType<Subclass>\nconst SubclassModel = getModelForClass(Subclass)\n\nexport default SubclassModel\n"
  },
  {
    "path": "src/models/2014/subrace.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, Int, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { AbilityScore } from './abilityScore'\nimport { Race } from './race'\nimport { Trait } from './trait'\n\n@ObjectType({ description: 'Bonus to an ability score provided by a subrace.' })\nexport class SubraceAbilityBonus {\n  @Field(() => AbilityScore, {\n    nullable: true,\n    description: 'The ability score receiving the bonus.'\n  })\n  @prop({ type: () => APIReference, required: true })\n  public ability_score!: APIReference\n\n  @Field(() => Int, { description: 'The bonus value to the ability score.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public bonus!: number\n}\n\n@ObjectType({ description: 'A subrace representing a specific heritage within a larger race.' })\n@srdModelOptions('2014-subraces')\nexport class Subrace {\n  @Field(() => [SubraceAbilityBonus], {\n    description: 'Ability score bonuses granted by this subrace.'\n  })\n  @prop({ type: () => [SubraceAbilityBonus], required: true })\n  public ability_bonuses!: SubraceAbilityBonus[]\n\n  @Field(() => String, { description: 'A description of the subrace.' })\n  @prop({ required: true, index: true, type: () => String })\n  public desc!: string\n\n  @Field(() => String, { description: 'The unique identifier for this subrace (e.g., high-elf).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the subrace (e.g., High Elf).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => Race, { nullable: true, description: 'The parent race for this subrace.' })\n  @prop({ type: () => APIReference, required: true })\n  public race!: APIReference\n\n  @Field(() => [Trait], {\n    nullable: true,\n    description: 'Racial traits associated with this subrace.'\n  })\n  @prop({ type: () => [APIReference] })\n  public racial_traits!: APIReference[]\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type SubraceDocument = DocumentType<Subrace>\nconst SubraceModel = getModelForClass(Subrace)\n\nexport default SubraceModel\n"
  },
  {
    "path": "src/models/2014/trait.ts",
    "content": "import { getModelForClass, modelOptions, prop, Severity } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, Int, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { AreaOfEffect } from '@/models/common/areaOfEffect'\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { AbilityScore } from './abilityScore'\nimport { DamageType } from './damageType'\nimport { Proficiency } from './proficiency'\nimport { Race } from './race'\nimport { Subrace } from './subrace'\nimport { Choice } from '../common/choice'\n\n@ObjectType({ description: 'Damage details for an action' })\n@modelOptions({ options: { allowMixed: Severity.ALLOW } })\nexport class ActionDamage {\n  @Field(() => DamageType, { nullable: true, description: 'The type of damage dealt.' })\n  @prop({ type: () => APIReference })\n  public damage_type!: APIReference\n\n  // Handled by ActionDamageResolver\n  @prop({ type: () => Object })\n  public damage_at_character_level?: Record<string, string>\n}\n\n@ObjectType({ description: 'Usage limit details for an action' })\nexport class Usage {\n  @Field(() => String, { description: \"Type of usage limit (e.g., 'per day').\" })\n  @prop({ required: true, index: true, type: () => String })\n  public type!: string\n\n  @Field(() => Int, { description: 'Number of times the action can be used.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public times!: number\n}\n\n@ObjectType({ description: 'DC details for a trait action (lacks dc_value).' })\nexport class TraitActionDC {\n  @Field(() => AbilityScore, { description: 'The ability score associated with this DC.' })\n  @prop({ type: () => APIReference, required: true })\n  public dc_type!: APIReference\n\n  @Field(() => String, { description: 'The result of a successful save against this DC.' })\n  @prop({ type: () => String, required: true })\n  public success_type!: 'none' | 'half' | 'other'\n}\n\n@ObjectType({ description: 'Represents an action associated with a trait (like a breath weapon).' })\nexport class Action {\n  @Field(() => String, { description: 'The name of the action.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, { description: 'Description of the action.' })\n  @prop({ required: true, index: true, type: () => String })\n  public desc!: string\n\n  @Field(() => Usage, { nullable: true, description: 'Usage limitations for the action.' })\n  @prop({ type: () => Usage })\n  public usage?: Usage\n\n  @Field(() => TraitActionDC, {\n    nullable: true,\n    description:\n      'The Difficulty Class (DC) associated with the action (value may not be applicable).'\n  })\n  @prop({ type: () => TraitActionDC })\n  public dc?: TraitActionDC\n\n  @Field(() => [ActionDamage], { nullable: true, description: 'Damage dealt by the action.' })\n  @prop({ type: () => [ActionDamage] })\n  public damage?: ActionDamage[]\n\n  @Field(() => AreaOfEffect, { nullable: true, description: 'The area of effect for the action.' })\n  @prop({ type: () => AreaOfEffect })\n  public area_of_effect?: AreaOfEffect\n}\n\n@ObjectType({ description: 'Details specific to certain traits.' })\nexport class TraitSpecific {\n  // Handled by TraitSpecificResolver\n  @prop({ type: () => Choice })\n  public subtrait_options?: Choice\n\n  // Handled by TraitSpecificResolver\n  @prop({ type: () => Choice })\n  public spell_options?: Choice\n\n  // Handled by TraitSpecificResolver\n  @prop({ type: () => APIReference })\n  public damage_type?: APIReference\n\n  @Field(() => Action, {\n    nullable: true,\n    description: 'Breath weapon action details, if applicable.'\n  })\n  @prop({ type: () => Action })\n  public breath_weapon?: Action\n}\n\n@ObjectType({\n  description: 'A racial or subracial trait providing specific benefits or abilities.'\n})\n@srdModelOptions('2014-traits')\nexport class Trait {\n  @Field(() => [String], { description: 'A description of the trait.' })\n  @prop({ required: true, index: true, type: () => [String] })\n  public desc!: string[]\n\n  @Field(() => String, { description: 'The unique identifier for this trait (e.g., darkvision).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the trait (e.g., Darkvision).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => [Proficiency], {\n    nullable: true,\n    description: 'Proficiencies granted by this trait.'\n  })\n  @prop({ type: () => [APIReference] })\n  public proficiencies?: APIReference[]\n\n  // Handled by TraitResolver\n  @prop({ type: () => Choice })\n  public proficiency_choices?: Choice\n\n  // Handled by TraitResolver\n  @prop({ type: () => Choice })\n  public language_options?: Choice\n\n  @Field(() => [Race], { nullable: true, description: 'Races that possess this trait.' })\n  @prop({ type: () => [APIReference], required: true })\n  public races!: APIReference[]\n\n  @Field(() => [Subrace], { nullable: true, description: 'Subraces that possess this trait.' })\n  @prop({ type: () => [APIReference], required: true })\n  public subraces!: APIReference[]\n\n  @Field(() => Trait, { nullable: true, description: 'A parent trait, if this is a sub-trait.' })\n  @prop({ type: () => APIReference })\n  public parent?: APIReference\n\n  @Field(() => TraitSpecific, {\n    nullable: true,\n    description: 'Specific details for this trait, if applicable.'\n  })\n  @prop({ type: () => TraitSpecific })\n  public trait_specific?: TraitSpecific\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type TraitDocument = DocumentType<Trait>\nconst TraitModel = getModelForClass(Trait)\n\nexport default TraitModel\n"
  },
  {
    "path": "src/models/2014/weaponProperty.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({\n  description: 'A property that can be applied to a weapon, modifying its use or characteristics.'\n})\n@srdModelOptions('2014-weapon-properties')\nexport class WeaponProperty {\n  @Field(() => [String], { description: 'A description of the weapon property.' })\n  @prop({ required: true, index: true, type: () => [String] })\n  public desc!: string[]\n\n  @Field(() => String, {\n    description: 'The unique identifier for this property (e.g., versatile).'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the property (e.g., Versatile).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type WeaponPropertyDocument = DocumentType<WeaponProperty>\nconst WeaponPropertyModel = getModelForClass(WeaponProperty)\n\nexport default WeaponPropertyModel\n"
  },
  {
    "path": "src/models/2024/abilityScore.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { Skill2024 } from '@/models/2024/skill'\nimport { APIReference } from '@/models/common/apiReference'\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({\n  description:\n    'An ability score representing a fundamental character attribute (e.g., Strength, Dexterity).'\n})\n@srdModelOptions('2024-ability-scores')\nexport class AbilityScore2024 {\n  @Field(() => String, {\n    description: 'A description of the ability score and its applications.'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public description!: string\n\n  @Field(() => String, { description: 'The full name of the ability score (e.g., Strength).' })\n  @prop({ required: true, index: true, type: () => String })\n  public full_name!: string\n\n  @Field(() => String, { description: 'The unique identifier for this ability score (e.g., str).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The abbreviated name of the ability score (e.g., STR).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => [Skill2024], { description: 'Skills associated with this ability score.' })\n  @prop({ type: () => [APIReference] })\n  public skills!: APIReference[]\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type AbilityScoreDocument = DocumentType<AbilityScore2024>\nconst AbilityScoreModel = getModelForClass(AbilityScore2024)\n\nexport default AbilityScoreModel\n"
  },
  {
    "path": "src/models/2024/alignment.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({\n  description: \"An alignment representing a character's moral and ethical beliefs.\"\n})\n@srdModelOptions('2024-alignments')\nexport class Alignment2024 {\n  @Field(() => String, { description: 'A description of the alignment.' })\n  @prop({ required: true, index: true, type: () => String })\n  public description!: string\n\n  @Field(() => String, {\n    description: 'A shortened representation of the alignment (e.g., LG, CE).'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public abbreviation!: string\n\n  @Field(() => String, {\n    description: 'The unique identifier for this alignment (e.g., lawful-good).'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, {\n    description: 'The name of the alignment (e.g., Lawful Good, Chaotic Evil).'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type AlignmentDocument = DocumentType<Alignment2024>\nconst AlignmentModel = getModelForClass(Alignment2024)\n\nexport default AlignmentModel\n"
  },
  {
    "path": "src/models/2024/background.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { Choice } from '@/models/common/choice'\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({ description: 'A reference to a feat with an optional note.' })\nexport class BackgroundFeatReference {\n  @Field(() => String)\n  @prop({ required: true, type: () => String })\n  public index!: string\n\n  @Field(() => String)\n  @prop({ required: true, type: () => String })\n  public name!: string\n\n  @Field(() => String)\n  @prop({ required: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { nullable: true })\n  @prop({ type: () => String })\n  public note?: string\n}\n\n@ObjectType({ description: 'A 2024 character background.' })\n@srdModelOptions('2024-backgrounds')\nexport class Background2024 {\n  @Field(() => String, { description: 'The unique identifier for this background.' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of this background.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @prop({ type: () => [APIReference], required: true })\n  public ability_scores!: APIReference[]\n\n  @prop({ type: () => BackgroundFeatReference, required: true })\n  public feat!: BackgroundFeatReference\n\n  @prop({ type: () => [APIReference], required: true })\n  public proficiencies!: APIReference[]\n\n  @prop({ type: () => [Choice] })\n  public proficiency_choices?: Choice[]\n\n  @prop({ type: () => [Choice] })\n  public equipment_options?: Choice[]\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type BackgroundDocument = DocumentType<Background2024>\nconst BackgroundModel = getModelForClass(Background2024)\n\nexport default BackgroundModel\n"
  },
  {
    "path": "src/models/2024/collection.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\n\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@srdModelOptions('2024-collections')\nexport class Collection2024 {\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n}\n\nexport type CollectionDocument = DocumentType<Collection2024>\nconst CollectionModel = getModelForClass(Collection2024)\n\nexport default CollectionModel\n"
  },
  {
    "path": "src/models/2024/condition.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({ description: 'A state that can affect a creature, such as Blinded or Prone.' })\n@srdModelOptions('2024-conditions')\nexport class Condition2024 {\n  @Field(() => String, { description: 'The unique identifier for this condition (e.g., blinded).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the condition (e.g., Blinded).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, { description: 'A description of the effects of the condition.' })\n  @prop({ required: true, type: () => String })\n  public description!: string\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type ConditionDocument = DocumentType<Condition2024>\nconst ConditionModel = getModelForClass(Condition2024)\n\nexport default ConditionModel\n"
  },
  {
    "path": "src/models/2024/damageType.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({ description: 'Represents a type of damage (e.g., Acid, Bludgeoning, Fire).' })\n@srdModelOptions('2024-damage-types')\nexport class DamageType2024 {\n  @Field(() => String, { description: 'The unique identifier for this damage type (e.g., acid).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the damage type (e.g., Acid).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, { description: 'A description of the damage type.' })\n  @prop({ required: true, type: () => String })\n  public description!: string\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type DamageTypeDocument = DocumentType<DamageType2024>\nconst DamageTypeModel = getModelForClass(DamageType2024)\n\nexport default DamageTypeModel\n"
  },
  {
    "path": "src/models/2024/equipment.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, Float, Int, ObjectType } from 'type-graphql'\n\nimport { EquipmentCategory2024 } from '@/models/2024/equipmentCategory'\nimport { APIReference } from '@/models/common/apiReference'\nimport { Damage } from '@/models/common/damage'\nimport { DifficultyClass } from '@/models/common/difficultyClass'\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({ description: 'Details about armor class.' })\nexport class ArmorClass {\n  @Field(() => Int, { description: 'Base armor class value.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public base!: number\n\n  @Field(() => Boolean, { description: 'Indicates if Dexterity bonus applies.' })\n  @prop({ required: true, index: true, type: () => Boolean })\n  public dex_bonus!: boolean\n\n  @Field(() => Int, { nullable: true, description: 'Maximum Dexterity bonus allowed.' })\n  @prop({ index: true, type: () => Number })\n  public max_bonus?: number\n}\n\n@ObjectType({ description: 'An item and its quantity within a container or bundle.' })\nexport class Content {\n  // Handled by ContentFieldResolver\n  @prop({ type: () => APIReference })\n  public item!: APIReference\n\n  @Field(() => Int, { description: 'The quantity of the item.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public quantity!: number\n}\n\n@ObjectType({ description: 'Cost of an item in coinage.' })\nexport class Cost {\n  @Field(() => Int, { description: 'The quantity of coins.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public quantity!: number\n\n  @Field(() => String, { description: 'The unit of coinage (e.g., gp, sp, cp).' })\n  @prop({ required: true, index: true, type: () => String })\n  public unit!: string\n}\n\n@ObjectType({ description: 'Range of a weapon (normal and long).' })\nexport class Range {\n  @Field(() => Int, { nullable: true, description: 'The long range of the weapon.' })\n  @prop({ index: true, type: () => Number })\n  public long?: number\n\n  @Field(() => Int, { description: 'The normal range of the weapon.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public normal!: number\n}\n\n@ObjectType({ description: 'Range for a thrown weapon.' })\nexport class ThrowRange {\n  @Field(() => Int, { description: 'The long range when thrown.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public long!: number\n\n  @Field(() => Int, { description: 'The normal range when thrown.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public normal!: number\n}\n\n@ObjectType({ description: 'How to utilize a tool.' })\nexport class Utilize {\n  @Field(() => String, { description: 'The name of the action.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => DifficultyClass, { description: 'The DC of the action.' })\n  @prop({ type: () => DifficultyClass })\n  public dc!: DifficultyClass\n}\n\n@ObjectType({\n  description: 'Base Equipment class for common fields, potentially used in Unions.'\n})\n@srdModelOptions('2024-equipment')\nexport class Equipment2024 {\n  // General fields\n\n  @Field(() => String, { description: 'The unique identifier for this equipment.' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the equipment.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => [String], { nullable: true, description: 'Description of the equipment.' })\n  @prop({ required: true, index: true, type: () => [String] })\n  public description?: string[]\n\n  @Field(() => [EquipmentCategory2024], {\n    description: 'The categories this equipment belongs to.'\n  })\n  @prop({ type: () => [APIReference] })\n  public equipment_categories!: APIReference[]\n\n  @prop({ index: true, type: () => APIReference })\n  public ammunition?: APIReference\n\n  @prop({ type: () => ArmorClass })\n  public armor_class?: ArmorClass\n\n  @prop({ type: () => [Content] })\n  public contents?: Content[]\n\n  @Field(() => Cost, { description: 'Cost of the equipment in coinage.' })\n  @prop({ type: () => Cost })\n  public cost!: Cost\n\n  @Field(() => Float, { nullable: true, description: 'Weight of the equipment in pounds.' })\n  @prop({ index: true, type: () => Number })\n  public weight?: number\n\n  @prop({ index: true, type: () => APIReference })\n  public ability?: APIReference\n\n  @prop({ index: true, type: () => [APIReference] })\n  public craft?: APIReference[]\n\n  @prop({ type: () => Damage })\n  public damage?: Damage\n\n  @prop({ index: true, type: () => String })\n  public doff_time?: string\n\n  @prop({ index: true, type: () => String })\n  public don_time?: string\n\n  @prop({ index: true, type: () => String })\n  public image?: string\n\n  @prop({ index: true, type: () => APIReference })\n  public mastery?: APIReference\n\n  @prop({ index: true, type: () => [String] })\n  public notes?: string[]\n\n  @prop({ type: () => [APIReference] })\n  public properties?: APIReference[]\n\n  @prop({ index: true, type: () => Number })\n  public quantity?: number\n\n  @prop({ type: () => Range })\n  public range?: Range\n\n  @prop({ index: true, type: () => Boolean })\n  public stealth_disadvantage?: boolean\n\n  @prop({ index: true, type: () => Number })\n  public str_minimum?: number\n\n  @prop({ type: () => ThrowRange })\n  public throw_range?: ThrowRange\n\n  @prop({ type: () => Damage })\n  public two_handed_damage?: Damage\n\n  @prop({ index: true, type: () => [Utilize] })\n  public utilize?: Utilize[]\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type EquipmentDocument = DocumentType<Equipment2024>\nconst EquipmentModel = getModelForClass(Equipment2024)\n\nexport default EquipmentModel\n"
  },
  {
    "path": "src/models/2024/equipmentCategory.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({\n  description: 'A category for grouping equipment (e.g., Weapon, Armor, Adventuring Gear).'\n})\n@srdModelOptions('2024-equipment-categories')\nexport class EquipmentCategory2024 {\n  // Handled by EquipmentCategoryResolver\n  @prop({ type: () => [APIReference], index: true })\n  public equipment!: APIReference[]\n\n  @Field(() => String, { description: 'The unique identifier for this category (e.g., weapon).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the category (e.g., Weapon).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type EquipmentCategoryDocument = DocumentType<EquipmentCategory2024>\nconst EquipmentCategoryModel = getModelForClass(EquipmentCategory2024)\n\nexport default EquipmentCategoryModel\n"
  },
  {
    "path": "src/models/2024/feat.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, Int, ObjectType } from 'type-graphql'\n\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { Choice } from '../common/choice'\n\n@ObjectType({ description: 'Prerequisites for a 2024 feat.' })\nexport class FeatPrerequisites2024 {\n  @Field(() => Int, { nullable: true, description: 'Minimum character level required.' })\n  @prop({ index: true, type: () => Number })\n  public minimum_level?: number\n\n  @Field(() => String, {\n    nullable: true,\n    description: 'Name of a feature (e.g. Spellcasting) required.'\n  })\n  @prop({ index: true, type: () => String })\n  public feature_named?: string\n}\n\n@ObjectType({ description: 'A 2024 feat.' })\n@srdModelOptions('2024-feats')\nexport class Feat2024 {\n  @Field(() => String, { description: 'The unique identifier for this feat.' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of this feat.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, { description: 'Description of the feat.' })\n  @prop({ required: true, type: () => String })\n  public description!: string\n\n  @Field(() => String, {\n    description: 'The type of feat (origin, general, fighting-style, epic-boon).'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public type!: string\n\n  @Field(() => String, { nullable: true, description: 'Repeatability note, if applicable.' })\n  @prop({ index: true, type: () => String })\n  public repeatable?: string\n\n  @Field(() => FeatPrerequisites2024, { nullable: true, description: 'Static prerequisites.' })\n  @prop({ type: () => FeatPrerequisites2024 })\n  public prerequisites?: FeatPrerequisites2024\n\n  @prop({ type: () => Choice })\n  public prerequisite_options?: Choice\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type FeatDocument = DocumentType<Feat2024>\nconst FeatModel = getModelForClass(Feat2024)\n\nexport default FeatModel\n"
  },
  {
    "path": "src/models/2024/language.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({ description: 'Represents a language spoken in the D&D world.' })\n@srdModelOptions('2024-languages')\nexport class Language2024 {\n  @Field(() => String, { description: 'The unique identifier for this language (e.g., draconic).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the language (e.g., Draconic).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => Boolean, { description: 'Whether the language is rare.' })\n  @prop({ required: true, index: true, type: () => Boolean })\n  public is_rare!: boolean\n\n  @Field(() => String, { description: 'A note about the language.' })\n  @prop({ required: true, index: true, type: () => String })\n  public note!: string\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type LanguageDocument = DocumentType<Language2024>\nconst LanguageModel = getModelForClass(Language2024)\n\nexport default LanguageModel\n"
  },
  {
    "path": "src/models/2024/magicItem.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { EquipmentCategory2024 } from './equipmentCategory'\n\n@ObjectType({ description: 'The rarity level of a 2024 magic item.' })\nexport class Rarity2024 {\n  @Field(() => String, {\n    description: 'The name of the rarity level (e.g., Common, Uncommon, Rare).'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n}\n\n@ObjectType({ description: 'An item imbued with magical properties in D&D 5e 2024.' })\n@srdModelOptions('2024-magic-items')\nexport class MagicItem2024 {\n  @Field(() => String, {\n    description: 'The unique identifier for this magic item (e.g., bag-of-holding).'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the magic item.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, { description: 'A description of the magic item.' })\n  @prop({ required: true, type: () => String })\n  public desc!: string\n\n  @Field(() => String, { nullable: true, description: 'URL of an image for the magic item.' })\n  @prop({ type: () => String, index: true })\n  public image?: string\n\n  @Field(() => EquipmentCategory2024, {\n    description: 'The category of equipment this magic item belongs to.'\n  })\n  @prop({ type: () => APIReference, index: true })\n  public equipment_category!: APIReference\n\n  @Field(() => Boolean, {\n    description: 'Whether this magic item requires attunement.'\n  })\n  @prop({ required: true, index: true, type: () => Boolean })\n  public attunement!: boolean\n\n  @Field(() => Boolean, {\n    description: 'Indicates if this magic item is a variant of another item.'\n  })\n  @prop({ required: true, index: true, type: () => Boolean })\n  public variant!: boolean\n\n  @Field(() => [MagicItem2024], {\n    nullable: true,\n    description: 'Other magic items that are variants of this item.'\n  })\n  @prop({ type: () => [APIReference], index: true })\n  public variants!: APIReference[]\n\n  @Field(() => Rarity2024, { description: 'The rarity of the magic item.' })\n  @prop({ required: true, index: true, type: () => Rarity2024 })\n  public rarity!: Rarity2024\n\n  @Field(() => String, {\n    nullable: true,\n    description: 'Class restriction for attunement (e.g., \"by a wizard\").'\n  })\n  @prop({ type: () => String, index: true })\n  public limited_to?: string\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type MagicItemDocument = DocumentType<MagicItem2024>\nconst MagicItemModel = getModelForClass(MagicItem2024)\n\nexport default MagicItemModel\n"
  },
  {
    "path": "src/models/2024/magicSchool.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({\n  description: 'A school of magic, representing a particular tradition like Evocation or Illusion.'\n})\n@srdModelOptions('2024-magic-schools')\nexport class MagicSchool2024 {\n  @Field(() => String, { description: 'A brief description of the school of magic.' })\n  @prop({ type: () => String, index: true })\n  public description!: string\n\n  @Field(() => String, { description: 'The unique identifier for this school (e.g., evocation).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the school (e.g., Evocation).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type MagicSchoolDocument = DocumentType<MagicSchool2024>\nconst MagicSchoolModel = getModelForClass(MagicSchool2024)\n\nexport default MagicSchoolModel\n"
  },
  {
    "path": "src/models/2024/proficiency.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({ description: 'A 2024 proficiency.' })\n@srdModelOptions('2024-proficiencies')\nexport class Proficiency2024 {\n  @Field(() => String, { description: 'The unique identifier for this proficiency.' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of this proficiency.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, { description: 'The type of proficiency (e.g., Skills, Tools).' })\n  @prop({ required: true, index: true, type: () => String })\n  public type!: string\n\n  @prop({ type: () => [APIReference], required: true })\n  public backgrounds!: APIReference[]\n\n  @prop({ type: () => [APIReference], required: true })\n  public classes!: APIReference[]\n\n  @Field(() => APIReference, { nullable: true, description: 'The referenced skill or tool.' })\n  @prop({ type: () => APIReference })\n  public reference?: APIReference\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type ProficiencyDocument = DocumentType<Proficiency2024>\nconst ProficiencyModel = getModelForClass(Proficiency2024)\n\nexport default ProficiencyModel\n"
  },
  {
    "path": "src/models/2024/skill.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { srdModelOptions } from '@/util/modelOptions'\n\nimport { AbilityScore2024 } from './abilityScore'\n\n@ObjectType({\n  description: 'A skill representing proficiency in a specific task (e.g., Athletics, Stealth).'\n})\n@srdModelOptions('2024-skills')\nexport class Skill2024 {\n  @Field(() => AbilityScore2024, { description: 'The ability score associated with this skill.' })\n  @prop({ type: () => APIReference, required: true })\n  public ability_score!: APIReference\n\n  @Field(() => String, { description: 'A description of the skill.' })\n  @prop({ required: true, index: true, type: () => String })\n  public description!: string\n\n  @Field(() => String, { description: 'The unique identifier for this skill (e.g., athletics).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the skill (e.g., Athletics).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type SkillDocument = DocumentType<Skill2024>\nconst SkillModel = getModelForClass(Skill2024)\n\nexport default SkillModel\n"
  },
  {
    "path": "src/models/2024/species.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { Choice } from '@/models/common/choice'\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({\n  description: 'A species representing a playable species in D&D 5e 2024.'\n})\n@srdModelOptions('2024-species')\nexport class Species2024 {\n  @Field(() => String, { description: 'The unique identifier for this species (e.g., elf).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the species (e.g., Elf).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, { description: 'The URL of the API resource.' })\n  @prop({ required: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'The creature type of this species (e.g., Humanoid).' })\n  @prop({ required: true, type: () => String })\n  public type!: string\n\n  @Field(() => String, { nullable: true, description: 'The size of this species.' })\n  @prop({ type: () => String })\n  public size?: string\n\n  @prop({ type: () => Choice })\n  public size_options?: Choice\n\n  @Field(() => Number, { description: 'The base walking speed of this species in feet.' })\n  @prop({ required: true, type: () => Number })\n  public speed!: number\n\n  @Field(() => [APIReference], { nullable: true, description: 'Traits granted by this species.' })\n  @prop({ type: () => [APIReference] })\n  public traits?: APIReference[]\n\n  @Field(() => [APIReference], {\n    nullable: true,\n    description: 'Subspecies available for this species.'\n  })\n  @prop({ type: () => [APIReference] })\n  public subspecies?: APIReference[]\n\n  @prop({ required: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type SpeciesDocument = DocumentType<Species2024>\nconst Species2024Model = getModelForClass(Species2024)\n\nexport default Species2024Model\n"
  },
  {
    "path": "src/models/2024/subclass.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, Int, ObjectType } from 'type-graphql'\n\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({ description: 'A feature granted by a 2024 subclass at a specific level.' })\nexport class SubclassFeature2024 {\n  @Field(() => String, { description: 'The name of the subclass feature.' })\n  @prop({ required: true, type: () => String })\n  public name!: string\n\n  @Field(() => Int, { description: 'The character level at which this feature is gained.' })\n  @prop({ required: true, type: () => Number })\n  public level!: number\n\n  @Field(() => String, { description: 'A description of the subclass feature.' })\n  @prop({ required: true, type: () => String })\n  public description!: string\n}\n\n@ObjectType({ description: 'A subclass representing a specialization of a class in D&D 5e 2024.' })\n@srdModelOptions('2024-subclasses')\nexport class Subclass2024 {\n  @Field(() => String, { description: 'The unique identifier for this subclass.' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the subclass.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, { description: 'A brief summary of the subclass.' })\n  @prop({ required: true, type: () => String })\n  public summary!: string\n\n  @Field(() => String, { description: 'A full description of the subclass.' })\n  @prop({ required: true, type: () => String })\n  public description!: string\n\n  @Field(() => [SubclassFeature2024], { description: 'Features granted by this subclass.' })\n  @prop({ required: true, type: () => [SubclassFeature2024] })\n  public features!: SubclassFeature2024[]\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type SubclassDocument = DocumentType<Subclass2024>\nconst SubclassModel = getModelForClass(Subclass2024)\n\nexport default SubclassModel\n"
  },
  {
    "path": "src/models/2024/subspecies.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({\n  description: 'A subspecies trait reference including the level at which it is gained.'\n})\nexport class SubspeciesTrait {\n  @Field(() => String, { description: 'The unique identifier for this trait.' })\n  @prop({ required: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of this trait.' })\n  @prop({ required: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, { description: 'The URL of the trait resource.' })\n  @prop({ required: true, type: () => String })\n  public url!: string\n\n  @Field(() => Number, { description: 'The character level at which this trait is gained.' })\n  @prop({ required: true, type: () => Number })\n  public level!: number\n}\n\n@ObjectType({\n  description: 'A subspecies representing a variant of a playable species in D&D 5e 2024.'\n})\n@srdModelOptions('2024-subspecies')\nexport class Subspecies2024 {\n  @Field(() => String, { description: 'The unique identifier for this subspecies.' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the subspecies.' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, { description: 'The URL of the API resource.' })\n  @prop({ required: true, type: () => String })\n  public url!: string\n\n  @Field(() => APIReference, { description: 'The parent species of this subspecies.' })\n  @prop({ type: () => APIReference, required: true })\n  public species!: APIReference\n\n  @Field(() => [SubspeciesTrait], { description: 'Traits granted by this subspecies.' })\n  @prop({ type: () => [SubspeciesTrait], required: true })\n  public traits!: SubspeciesTrait[]\n\n  @Field(() => APIReference, {\n    nullable: true,\n    description: 'The damage type associated with this subspecies (Dragonborn only).'\n  })\n  @prop({ type: () => APIReference })\n  public damage_type?: APIReference\n\n  @prop({ required: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type SubspeciesDocument = DocumentType<Subspecies2024>\nconst Subspecies2024Model = getModelForClass(Subspecies2024)\n\nexport default Subspecies2024Model\n"
  },
  {
    "path": "src/models/2024/trait.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { Choice } from '@/models/common/choice'\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({\n  description: 'A trait granted by a species or subspecies in D&D 5e 2024.'\n})\n@srdModelOptions('2024-traits')\nexport class Trait2024 {\n  @Field(() => String, { description: 'The unique identifier for this trait (e.g., darkvision).' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the trait (e.g., Darkvision).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, { description: 'The URL of the API resource.' })\n  @prop({ required: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'A description of the trait.' })\n  @prop({ required: true, type: () => String })\n  public description!: string\n\n  @Field(() => [APIReference], {\n    description: 'The species that grant this trait.'\n  })\n  @prop({ type: () => [APIReference], required: true })\n  public species!: APIReference[]\n\n  @Field(() => [APIReference], {\n    nullable: true,\n    description: 'The subspecies that grant this trait.'\n  })\n  @prop({ type: () => [APIReference] })\n  public subspecies?: APIReference[]\n\n  @prop({ type: () => Choice })\n  public proficiency_choices?: Choice\n\n  @Field(() => Number, { nullable: true, description: 'Speed override granted by this trait.' })\n  @prop({ type: () => Number })\n  public speed?: number\n\n  @prop({ required: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type TraitDocument = DocumentType<Trait2024>\nconst Trait2024Model = getModelForClass(Trait2024)\n\nexport default Trait2024Model\n"
  },
  {
    "path": "src/models/2024/weaponMasteryProperty.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({\n  description:\n    'Each weapon has a mastery property, which is usable only by a character who has a feature, such as Weapon Mastery, that unlocks the property for the character'\n})\n@srdModelOptions('2024-weapon-mastery-properties')\nexport class WeaponMasteryProperty2024 {\n  @Field(() => String, { description: 'A description of the weapon mastery property.' })\n  @prop({ required: true, index: true, type: () => String })\n  public description!: string\n\n  @Field(() => String, {\n    description: 'The unique identifier for this mastery property (e.g., cleave).'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the mastery property (e.g., Cleave).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type WeaponMasteryPropertyDocument = DocumentType<WeaponMasteryProperty2024>\nconst WeaponMasteryPropertyModel = getModelForClass(WeaponMasteryProperty2024)\n\nexport default WeaponMasteryPropertyModel\n"
  },
  {
    "path": "src/models/2024/weaponProperty.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\nimport { DocumentType } from '@typegoose/typegoose/lib/types'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { srdModelOptions } from '@/util/modelOptions'\n\n@ObjectType({\n  description: 'A property that can be applied to a weapon, modifying its use or characteristics.'\n})\n@srdModelOptions('2024-weapon-properties')\nexport class WeaponProperty2024 {\n  @Field(() => String, { description: 'A description of the weapon property.' })\n  @prop({ required: true, index: true, type: () => String })\n  public description!: string\n\n  @Field(() => String, {\n    description: 'The unique identifier for this property (e.g., versatile).'\n  })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the property (e.g., Versatile).' })\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @prop({ required: true, index: true, type: () => String })\n  public url!: string\n\n  @Field(() => String, { description: 'Timestamp of the last update.' })\n  @prop({ required: true, index: true, type: () => String })\n  public updated_at!: string\n}\n\nexport type WeaponPropertyDocument = DocumentType<WeaponProperty2024>\nconst WeaponPropertyModel = getModelForClass(WeaponProperty2024)\n\nexport default WeaponPropertyModel\n"
  },
  {
    "path": "src/models/common/apiReference.ts",
    "content": "import { prop } from '@typegoose/typegoose'\nimport { Field, ObjectType } from 'type-graphql'\n\n// Base class representing a reference to another resource\n@ObjectType({ description: 'Reference to another API resource' })\nexport class APIReference {\n  @Field(() => String, { description: 'The resource index for the API resource' })\n  @prop({ required: true, index: true, type: () => String })\n  public index!: string\n\n  @Field(() => String, { description: 'The name of the API resource' })\n  @prop({ required: true, type: () => String })\n  public name!: string\n\n  @Field(() => String, { description: 'The URL of the API resource' })\n  @prop({ required: true, type: () => String })\n  public url!: string\n}\n"
  },
  {
    "path": "src/models/common/areaOfEffect.ts",
    "content": "import { prop } from '@typegoose/typegoose'\nimport { Field, Int, ObjectType } from 'type-graphql'\n\n@ObjectType({ description: 'Defines an area of effect for spells or abilities.' })\nexport class AreaOfEffect {\n  @Field(() => Int, { description: 'The size of the area of effect (e.g., radius in feet).' })\n  @prop({ required: true, type: () => Number })\n  public size!: number\n\n  @Field(() => String, { description: 'The shape of the area of effect.' })\n  @prop({ required: true, index: true, type: () => String })\n  public type!: 'sphere' | 'cube' | 'cylinder' | 'line' | 'cone'\n}\n"
  },
  {
    "path": "src/models/common/choice.ts",
    "content": "import { getModelForClass, prop } from '@typegoose/typegoose'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { Damage } from '@/models/common/damage'\nimport { DifficultyClass } from '@/models/common/difficultyClass'\n\n// Option Set Classes\nexport class OptionSet {\n  @prop({ required: true, index: true, type: () => String })\n  public option_set_type!: 'equipment_category' | 'resource_list' | 'options_array'\n}\n\nexport class EquipmentCategoryOptionSet extends OptionSet {\n  @prop({ type: () => APIReference, required: true, index: true })\n  public equipment_category!: APIReference\n}\n\nexport class ResourceListOptionSet extends OptionSet {\n  @prop({ required: true, index: true, type: () => String })\n  public resource_list_url!: string\n}\n\nexport class OptionsArrayOptionSet extends OptionSet {\n  @prop({ type: () => [Option], required: true, index: true })\n  public options!: Option[]\n}\n\n// Option Classes\nexport class Option {\n  @prop({ required: true, index: true, type: () => String })\n  public option_type!: string\n}\n\nexport class ReferenceOption extends Option {\n  @prop({ type: () => APIReference, required: true, index: true })\n  public item!: APIReference\n}\n\nexport class ActionOption extends Option {\n  @prop({ required: true, index: true, type: () => String })\n  public action_name!: string\n\n  @prop({ required: true, index: true, type: () => Number })\n  public count!: number | string\n\n  @prop({ required: true, index: true, type: () => String })\n  public type!: 'melee' | 'ranged' | 'ability' | 'magic'\n\n  @prop({ index: true, type: () => String })\n  public notes?: string\n}\n\nexport class MultipleOption extends Option {\n  @prop({ type: () => [Option], required: true, index: true })\n  public items!: Option[]\n}\n\nexport class StringOption extends Option {\n  @prop({ required: true, index: true, type: () => String })\n  public string!: string\n}\n\nexport class IdealOption extends Option {\n  @prop({ required: true, index: true, type: () => String })\n  public desc!: string\n\n  @prop({ type: () => [APIReference], required: true, index: true })\n  public alignments!: APIReference[]\n}\n\nexport class CountedReferenceOption extends Option {\n  @prop({ required: true, index: true, type: () => Number })\n  public count!: number\n\n  @prop({ type: () => APIReference, required: true, index: true })\n  public of!: APIReference\n\n  @prop({\n    type: () => [\n      {\n        type: { type: String, required: true },\n        proficiency: { type: () => APIReference }\n      }\n    ],\n    index: true\n  })\n  public prerequisites?: {\n    type: 'proficiency'\n    proficiency?: APIReference\n  }[]\n}\n\nexport class ScorePrerequisiteOption extends Option {\n  @prop({ type: () => APIReference, required: true, index: true })\n  public ability_score!: APIReference\n\n  @prop({ required: true, index: true, type: () => Number })\n  public minimum_score!: number\n}\n\nexport class AbilityBonusOption extends Option {\n  @prop({ type: () => APIReference, required: true, index: true })\n  public ability_score!: APIReference\n\n  @prop({ required: true, index: true, type: () => Number })\n  public bonus!: number\n}\n\nexport class BreathOption extends Option {\n  @prop({ required: true, index: true, type: () => String })\n  public name!: string\n\n  @prop({ type: () => DifficultyClass, required: true, index: true })\n  public dc!: DifficultyClass\n\n  @prop({ type: () => [Damage], index: true })\n  public damage?: Damage[]\n}\n\nexport class DamageOption extends Option {\n  @prop({ type: () => APIReference, required: true, index: true })\n  public damage_type!: APIReference\n\n  @prop({ required: true, index: true, type: () => String })\n  public damage_dice!: string\n\n  @prop({ index: true, type: () => String })\n  public notes?: string\n}\n\nexport class Choice {\n  @prop({ type: () => String, required: true })\n  public desc!: string\n\n  @prop({ type: () => Number, required: true })\n  public choose!: number\n\n  @prop({ type: () => String, required: true })\n  public type!: string\n\n  @prop({ type: () => OptionSet, required: true })\n  public from!: OptionSet\n}\n\nexport class ChoiceOption extends Option {\n  @prop({ type: () => Choice, required: true, index: true })\n  public choice!: Choice\n}\n\nexport class MoneyOption extends Option {\n  @prop({ required: true, index: true, type: () => Number })\n  public count!: number\n\n  @prop({ required: true, index: true, type: () => String })\n  public unit!: string\n}\n\n// Export models\nexport const OptionSetModel = getModelForClass(OptionSet)\nexport const OptionModel = getModelForClass(Option)\nexport const ChoiceModel = getModelForClass(Choice)\n"
  },
  {
    "path": "src/models/common/damage.ts",
    "content": "import { prop } from '@typegoose/typegoose'\nimport { Field, ObjectType } from 'type-graphql'\n\nimport { DamageType } from '@/models/2014/damageType'\nimport { APIReference } from '@/models/common/apiReference'\n\n@ObjectType({ description: 'Represents damage dealt by an ability, spell, or weapon.' })\nexport class Damage {\n  @Field(() => DamageType, { nullable: true, description: 'The type of damage.' })\n  @prop({ type: () => APIReference })\n  public damage_type!: APIReference // This should reference DamageType, not any APIReference\n\n  @Field(() => String, { description: 'The damage dice roll (e.g., 3d6).' })\n  @prop({ required: true, index: true, type: () => String })\n  public damage_dice!: string\n}\n"
  },
  {
    "path": "src/models/common/difficultyClass.ts",
    "content": "import { prop } from '@typegoose/typegoose'\nimport { Field, Int, ObjectType } from 'type-graphql'\n\nimport { APIReference } from './apiReference' // Assuming apiReference.ts is in the same directory\nimport { AbilityScore } from '../2014/abilityScore' // Path to AbilityScore model\n\n@ObjectType({\n  description:\n    'Represents a Difficulty Class (DC) for saving throws or ability checks where a value is expected.'\n})\nexport class DifficultyClass {\n  @Field(() => AbilityScore, { description: 'The ability score associated with this DC.' })\n  @prop({ type: () => APIReference })\n  public dc_type!: APIReference\n\n  @Field(() => Int, { description: 'The value of the DC.' })\n  @prop({ required: true, index: true, type: () => Number })\n  public dc_value!: number\n\n  @Field(() => String, { description: 'The result of a successful save against this DC.' })\n  @prop({ required: true, index: true, type: () => String })\n  public success_type!: 'none' | 'half' | 'other'\n}\n"
  },
  {
    "path": "src/public/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>D&D 5th Edition API</title>\n\n    <!-- Primary Meta Tags -->\n    <meta name=\"title\" content=\"D&D 5th Edition API\" />\n    <meta name=\"description\" content=\"REST API to access D&D 5th Edition SRD database\" />\n    <meta name=\"keywords\" content=\"Dungeons, Dragons, 5th, Edition, API, SRD\" />\n    <meta name=\"theme-color\" content=\"#D81921\" />\n\n    <!-- Open Graph / Facebook -->\n    <meta property=\"og:type\" content=\"website\" />\n    <meta property=\"og:url\" content=\"https://www.dnd5eapi.co/\" />\n    <meta property=\"og:title\" content=\"D&D 5th Edition API\" />\n    <meta property=\"og:description\" content=\"REST API to access D&D 5th Edition SRD database\" />\n    <meta property=\"og:image\" content=\"/public/DnD-5e-meta-4k.png\" />\n\n    <!-- Twitter -->\n    <meta property=\"twitter:card\" content=\"summary_large_image\" />\n    <meta property=\"twitter:url\" content=\"https://www.dnd5eapi.co/\" />\n    <meta property=\"twitter:title\" content=\"D&D 5th Edition API\" />\n    <meta\n      property=\"twitter:description\"\n      content=\"REST API to access D&D 5th Edition SRD database\"\n    />\n    <meta property=\"twitter:image\" content=\"/public/dnd-5e-meta.jpg\" />\n\n    <link rel=\"stylesheet\" href=\"/css/custom.css\" />\n    <link rel=\"shortcut icon\" href=\"/public/favicon.ico\" type=\"image/x-icon\" />\n  </head>\n\n  <body>\n    <!-- Navbar -->\n    <nav class=\"navbar\">\n      <div class=\"nav-container\">\n        <button class=\"menu-toggle\" aria-label=\"Toggle menu\">\n          <span></span>\n          <span></span>\n          <span></span>\n        </button>\n        <ul class=\"nav-links\">\n          <li class=\"active\"><a href=\"/\">Home</a></li>\n          <li><a href=\"https://5e-bits.github.io/docs\">Documentation</a></li>\n          <li><a href=\"https://discord.gg/TQuYTv7\">Chat</a></li>\n          <li><a href=\"https://github.com/bagelbits/5e-srd-api\">Contribute</a></li>\n        </ul>\n      </div>\n    </nav>\n\n    <!-- Main Content -->\n    <section id=\"home\">\n      <div class=\"header\">\n        <h1>D&D 5e API</h1>\n        <h2>The 5th Edition Dungeons and Dragons API</h2>\n      </div>\n\n      <div class=\"cta\">\n        <p>\n          Just a simple api for things within the Official 5th Edition SRD<br />\n          and easily accessible through a modern RESTful API.\n        </p>\n        <p>Enjoy the D&D 5th Edition API!</p>\n      </div>\n\n      <div class=\"content\">\n        <div class=\"interactive-section\">\n          <h1>Try it now!</h1>\n          <div class=\"api-input\">\n            <span class=\"api-prefix\">https://www.dnd5eapi.co/api/2014/</span>\n            <input id=\"interactive\" type=\"text\" placeholder=\"spells/acid-arrow/\" />\n            <button onclick=\"interactiveCall();return false;\">submit</button>\n          </div>\n\n          <div class=\"hints\">\n            Need a hint? try\n            <a href=\"#\" onClick=\"update('classes/');return false;\">classes/</a>,\n            <a href=\"#\" onClick=\"update('features/');return false;\">features/</a>,\n            <a href=\"#\" onClick=\"update('monsters/adult-black-dragon/');return false;\"\n              >monsters/adult-black-dragon/</a\n            >\n            or\n            <a href=\"#\" onClick=\"update('spells/?name=Acid+Arrow');return false;\"\n              >spells/?name=Acid+Arrow</a\n            >\n          </div>\n\n          <p class=\"result-label\">Resource for <span id=\"interactive_name\">Acid Arrow</span></p>\n          <pre id=\"interactive_output\" class=\"output\"></pre>\n        </div>\n      </div>\n    </section>\n\n    <!-- Replace old scripts with modern JS -->\n    <script>\n      // Update input field and trigger API call\n      function update(call) {\n        document.getElementById('interactive').value = call\n        interactiveCall()\n      }\n\n      // Make API call and update the output\n      async function interactiveCall() {\n        const input = document.getElementById('interactive')\n        const nameSpan = document.getElementById('interactive_name')\n        const output = document.getElementById('interactive_output')\n\n        let content = input.value\n        if (!content) {\n          content = 'spells/acid-arrow/'\n        }\n\n        try {\n          const response = await fetch('api/' + content)\n\n          if (response.ok) {\n            const data = await response.json()\n            nameSpan.textContent = data.name ? `${data.name}:` : 'this request:'\n            output.textContent = JSON.stringify(data, null, 2)\n          } else {\n            output.textContent = `${response.status} ${response.statusText}`\n          }\n        } catch (err) {\n          output.textContent = 'Error: ' + err.message\n        }\n      }\n\n      // Initialize with default data\n      document.addEventListener('DOMContentLoaded', interactiveCall)\n    </script>\n\n    <!-- Google Analytics -->\n    <script>\n      ;(function (i, s, o, g, r, a, m) {\n        i['GoogleAnalyticsObject'] = r\n        ;(i[r] =\n          i[r] ||\n          function () {\n            ;(i[r].q = i[r].q || []).push(arguments)\n          }),\n          (i[r].l = 1 * new Date())\n        ;(a = s.createElement(o)), (m = s.getElementsByTagName(o)[0])\n        a.async = 1\n        a.src = g\n        m.parentNode.insertBefore(a, m)\n      })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga')\n\n      ga('create', 'UA-89541269-1', 'auto')\n      ga('send', 'pageview')\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "src/routes/api/2014/abilityScores.ts",
    "content": "import express from 'express'\n\nimport AbilityScoreController from '@/controllers/api/2014/abilityScoreController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  AbilityScoreController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  AbilityScoreController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/alignments.ts",
    "content": "import * as express from 'express'\n\nimport AlignmentController from '@/controllers/api/2014/alignmentController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  AlignmentController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  AlignmentController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/backgrounds.ts",
    "content": "import * as express from 'express'\n\nimport BackgroundController from '@/controllers/api/2014/backgroundController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  BackgroundController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  BackgroundController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/classes.ts",
    "content": "import * as express from 'express'\n\nimport * as ClassController from '@/controllers/api/2014/classController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  ClassController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  ClassController.show(req, res, next)\n})\n\nrouter.get('/:index/subclasses', function (req, res, next) {\n  ClassController.showSubclassesForClass(req, res, next)\n})\nrouter.get('/:index/starting-equipment', function (req, res, next) {\n  ClassController.showStartingEquipmentForClass(req, res, next)\n})\nrouter.get('/:index/spellcasting', function (req, res, next) {\n  ClassController.showSpellcastingForClass(req, res, next)\n})\nrouter.get('/:index/spells', function (req, res, next) {\n  ClassController.showSpellsForClass(req, res, next)\n})\nrouter.get('/:index/features', function (req, res, next) {\n  ClassController.showFeaturesForClass(req, res, next)\n})\nrouter.get('/:index/proficiencies', function (req, res, next) {\n  ClassController.showProficienciesForClass(req, res, next)\n})\nrouter.get('/:index/multi-classing', function (req, res, next) {\n  ClassController.showMulticlassingForClass(req, res, next)\n})\n\nrouter.get('/:index/levels/:level/spells', function (req, res, next) {\n  ClassController.showSpellsForClassAndLevel(req, res, next)\n})\nrouter.get('/:index/levels/:level/features', function (req, res, next) {\n  ClassController.showFeaturesForClassAndLevel(req, res, next)\n})\nrouter.get('/:index/levels/:level', function (req, res, next) {\n  ClassController.showLevelForClass(req, res, next)\n})\nrouter.get('/:index/levels', function (req, res, next) {\n  ClassController.showLevelsForClass(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/conditions.ts",
    "content": "import * as express from 'express'\n\nimport ConditionController from '@/controllers/api/2014/conditionController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  ConditionController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  ConditionController.show(req, res, next)\n})\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/damageTypes.ts",
    "content": "import * as express from 'express'\n\nimport DamageTypeController from '@/controllers/api/2014/damageTypeController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  DamageTypeController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  DamageTypeController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/equipment.ts",
    "content": "import * as express from 'express'\n\nimport EquipmentController from '@/controllers/api/2014/equipmentController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  EquipmentController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  EquipmentController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/equipmentCategories.ts",
    "content": "import * as express from 'express'\n\nimport EquipmentCategoryController from '@/controllers/api/2014/equipmentCategoryController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  EquipmentCategoryController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  EquipmentCategoryController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/feats.ts",
    "content": "import * as express from 'express'\n\nimport FeatController from '@/controllers/api/2014/featController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  FeatController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  FeatController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/features.ts",
    "content": "import * as express from 'express'\n\nimport FeatureController from '@/controllers/api/2014/featureController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  FeatureController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  FeatureController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/images.ts",
    "content": "import * as express from 'express'\n\nimport ImageController from '@/controllers/api/imageController'\n\nconst router = express.Router()\n\nrouter.get('/*splat', function (req, res, next) {\n  ImageController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/languages.ts",
    "content": "import * as express from 'express'\n\nimport LanguageController from '@/controllers/api/2014/languageController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  LanguageController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  LanguageController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/magicItems.ts",
    "content": "import * as express from 'express'\n\nimport * as MagicItemController from '@/controllers/api/2014/magicItemController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  MagicItemController.index(req, res, next)\n})\nrouter.get('/:index', function (req, res, next) {\n  MagicItemController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/magicSchools.ts",
    "content": "import * as express from 'express'\n\nimport MagicSchoolController from '@/controllers/api/2014/magicSchoolController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  MagicSchoolController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  MagicSchoolController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/monsters.ts",
    "content": "import * as express from 'express'\n\nimport * as MonsterController from '@/controllers/api/2014/monsterController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  MonsterController.index(req, res, next)\n})\nrouter.get('/:index', function (req, res, next) {\n  MonsterController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/proficiencies.ts",
    "content": "import * as express from 'express'\n\nimport ProficiencyController from '@/controllers/api/2014/proficiencyController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  ProficiencyController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  ProficiencyController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/races.ts",
    "content": "import * as express from 'express'\n\nimport * as RaceController from '@/controllers/api/2014/raceController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  RaceController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  RaceController.show(req, res, next)\n})\n\nrouter.get('/:index/subraces', function (req, res, next) {\n  RaceController.showSubracesForRace(req, res, next)\n})\nrouter.get('/:index/proficiencies', function (req, res, next) {\n  RaceController.showProficienciesForRace(req, res, next)\n})\nrouter.get('/:index/traits', function (req, res, next) {\n  RaceController.showTraitsForRace(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/ruleSections.ts",
    "content": "import * as express from 'express'\n\nimport * as RuleSectionController from '@/controllers/api/2014/ruleSectionController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  RuleSectionController.index(req, res, next)\n})\nrouter.get('/:index', function (req, res, next) {\n  RuleSectionController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/rules.ts",
    "content": "import * as express from 'express'\n\nimport * as RuleController from '@/controllers/api/2014/ruleController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  RuleController.index(req, res, next)\n})\nrouter.get('/:index', function (req, res, next) {\n  RuleController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/skills.ts",
    "content": "import * as express from 'express'\n\nimport SkillController from '@/controllers/api/2014/skillController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  SkillController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  SkillController.show(req, res, next)\n})\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/spells.ts",
    "content": "import * as express from 'express'\n\nimport * as SpellController from '@/controllers/api/2014/spellController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  SpellController.index(req, res, next)\n})\nrouter.get('/:index', function (req, res, next) {\n  SpellController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/subclasses.ts",
    "content": "import * as express from 'express'\n\nimport * as SubclassController from '@/controllers/api/2014/subclassController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  SubclassController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  SubclassController.show(req, res, next)\n})\n\nrouter.get('/:index/features', function (req, res, next) {\n  SubclassController.showFeaturesForSubclass(req, res, next)\n})\n\nrouter.get('/:index/levels/:level/features', function (req, res, next) {\n  SubclassController.showFeaturesForSubclassAndLevel(req, res, next)\n})\nrouter.get('/:index/levels/:level', function (req, res, next) {\n  SubclassController.showLevelForSubclass(req, res, next)\n})\nrouter.get('/:index/levels', function (req, res, next) {\n  SubclassController.showLevelsForSubclass(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/subraces.ts",
    "content": "import * as express from 'express'\n\nimport * as SubraceController from '@/controllers/api/2014/subraceController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  SubraceController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  SubraceController.show(req, res, next)\n})\n\nrouter.get('/:index/traits', function (req, res, next) {\n  SubraceController.showTraitsForSubrace(req, res, next)\n})\nrouter.get('/:index/proficiencies', function (req, res, next) {\n  SubraceController.showProficienciesForSubrace(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/traits.ts",
    "content": "import * as express from 'express'\n\nimport TraitController from '@/controllers/api/2014/traitController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  TraitController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  TraitController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014/weaponProperties.ts",
    "content": "import * as express from 'express'\n\nimport WeaponPropertyController from '@/controllers/api/2014/weaponPropertyController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  WeaponPropertyController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  WeaponPropertyController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2014.ts",
    "content": "import express from 'express'\n\nimport { index } from '@/controllers/api/v2014Controller'\n\nimport AbilityScoresHandler from './2014/abilityScores'\nimport AlignmentsHandler from './2014/alignments'\nimport BackgroundsHandler from './2014/backgrounds'\nimport ClassesHandler from './2014/classes'\nimport ConditionsHandler from './2014/conditions'\nimport DamageTypesHandler from './2014/damageTypes'\nimport EquipmentHandler from './2014/equipment'\nimport EquipmentCategoriesHandler from './2014/equipmentCategories'\nimport FeatsHandler from './2014/feats'\nimport FeaturesHandler from './2014/features'\nimport ImageHandler from './2014/images'\nimport LanguagesHandler from './2014/languages'\nimport MagicItemsHandler from './2014/magicItems'\nimport MagicSchoolsHandler from './2014/magicSchools'\nimport MonstersHandler from './2014/monsters'\nimport ProficienciesHandler from './2014/proficiencies'\nimport RacesHandler from './2014/races'\nimport RulesHandler from './2014/rules'\nimport RuleSectionsHandler from './2014/ruleSections'\nimport SkillsHandler from './2014/skills'\nimport SpellsHandler from './2014/spells'\nimport SubclassesHandler from './2014/subclasses'\nimport SubracesHandler from './2014/subraces'\nimport TraitsHandler from './2014/traits'\nimport WeaponPropertiesHandler from './2014/weaponProperties'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  index(req, res, next)\n})\n\nrouter.use('/ability-scores', AbilityScoresHandler)\nrouter.use('/alignments', AlignmentsHandler)\nrouter.use('/backgrounds', BackgroundsHandler)\nrouter.use('/classes', ClassesHandler)\nrouter.use('/conditions', ConditionsHandler)\nrouter.use('/damage-types', DamageTypesHandler)\nrouter.use('/equipment-categories', EquipmentCategoriesHandler)\nrouter.use('/equipment', EquipmentHandler)\nrouter.use('/feats', FeatsHandler)\nrouter.use('/features', FeaturesHandler)\nrouter.use('/images', ImageHandler)\nrouter.use('/languages', LanguagesHandler)\nrouter.use('/magic-items', MagicItemsHandler)\nrouter.use('/magic-schools', MagicSchoolsHandler)\nrouter.use('/monsters', MonstersHandler)\nrouter.use('/proficiencies', ProficienciesHandler)\nrouter.use('/races', RacesHandler)\nrouter.use('/rules', RulesHandler)\nrouter.use('/rule-sections', RuleSectionsHandler)\nrouter.use('/skills', SkillsHandler)\nrouter.use('/spells', SpellsHandler)\nrouter.use('/subclasses', SubclassesHandler)\nrouter.use('/subraces', SubracesHandler)\nrouter.use('/traits', TraitsHandler)\nrouter.use('/weapon-properties', WeaponPropertiesHandler)\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/abilityScores.ts",
    "content": "import express from 'express'\n\nimport AbilityScoreController from '@/controllers/api/2024/abilityScoreController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  AbilityScoreController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  AbilityScoreController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/alignments.ts",
    "content": "import express from 'express'\n\nimport AlignmentController from '@/controllers/api/2024/alignmentController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  AlignmentController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  AlignmentController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/backgrounds.ts",
    "content": "import * as express from 'express'\n\nimport BackgroundController from '@/controllers/api/2024/backgroundController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  BackgroundController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  BackgroundController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/conditions.ts",
    "content": "import express from 'express'\n\nimport ConditionController from '@/controllers/api/2024/conditionController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  ConditionController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  ConditionController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/damageTypes.ts",
    "content": "import express from 'express'\n\nimport DamageTypeController from '@/controllers/api/2024/damageTypeController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  DamageTypeController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  DamageTypeController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/equipment.ts",
    "content": "import * as express from 'express'\n\nimport EquipmentController from '@/controllers/api/2024/equipmentController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  EquipmentController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  EquipmentController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/equipmentCategories.ts",
    "content": "import * as express from 'express'\n\nimport EquipmentCategoryController from '@/controllers/api/2024/equipmentCategoryController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  EquipmentCategoryController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  EquipmentCategoryController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/feats.ts",
    "content": "import * as express from 'express'\n\nimport FeatController from '@/controllers/api/2024/featController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  FeatController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  FeatController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/languages.ts",
    "content": "import express from 'express'\n\nimport LanguageController from '@/controllers/api/2024/languageController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  LanguageController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  LanguageController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/magicItems.ts",
    "content": "import * as express from 'express'\n\nimport MagicItemController from '@/controllers/api/2024/magicItemController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  MagicItemController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  MagicItemController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/magicSchools.ts",
    "content": "import express from 'express'\n\nimport MagicSchoolController from '@/controllers/api/2024/magicSchoolController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  MagicSchoolController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  MagicSchoolController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/proficiencies.ts",
    "content": "import * as express from 'express'\n\nimport ProficiencyController from '@/controllers/api/2024/proficiencyController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  ProficiencyController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  ProficiencyController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/skills.ts",
    "content": "import * as express from 'express'\n\nimport SkillController from '@/controllers/api/2024/skillController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  SkillController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  SkillController.show(req, res, next)\n})\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/species.ts",
    "content": "import * as express from 'express'\n\nimport * as SpeciesController from '@/controllers/api/2024/speciesController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  SpeciesController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  SpeciesController.show(req, res, next)\n})\n\nrouter.get('/:index/subspecies', function (req, res, next) {\n  SpeciesController.showSubspeciesForSpecies(req, res, next)\n})\n\nrouter.get('/:index/traits', function (req, res, next) {\n  SpeciesController.showTraitsForSpecies(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/subclasses.ts",
    "content": "import * as express from 'express'\n\nimport SubclassController from '@/controllers/api/2024/subclassController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  SubclassController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  SubclassController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/subspecies.ts",
    "content": "import * as express from 'express'\n\nimport * as SubspeciesController from '@/controllers/api/2024/subspeciesController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  SubspeciesController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  SubspeciesController.show(req, res, next)\n})\n\nrouter.get('/:index/traits', function (req, res, next) {\n  SubspeciesController.showTraitsForSubspecies(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/traits.ts",
    "content": "import * as express from 'express'\n\nimport TraitController from '@/controllers/api/2024/traitController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  TraitController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  TraitController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/weaponMasteryProperties.ts",
    "content": "import express from 'express'\n\nimport WeaponMasteryPropertyController from '@/controllers/api/2024/weaponMasteryPropertyController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  WeaponMasteryPropertyController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  WeaponMasteryPropertyController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024/weaponProperty.ts",
    "content": "import express from 'express'\n\nimport WeaponPropertyController from '@/controllers/api/2024/weaponPropertyController'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  WeaponPropertyController.index(req, res, next)\n})\n\nrouter.get('/:index', function (req, res, next) {\n  WeaponPropertyController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/2024.ts",
    "content": "import express from 'express'\n\nimport { index } from '@/controllers/api/v2024Controller'\n\nimport AbilityScoresHandler from './2024/abilityScores'\nimport AlignmentsHandler from './2024/alignments'\nimport BackgroundsHandler from './2024/backgrounds'\nimport ConditionsHandler from './2024/conditions'\nimport DamageTypesHandler from './2024/damageTypes'\nimport EquipmentHandler from './2024/equipment'\nimport EquipmentCategoriesHandler from './2024/equipmentCategories'\nimport FeatsHandler from './2024/feats'\nimport LanguagesHandler from './2024/languages'\nimport MagicItemsHandler from './2024/magicItems'\nimport MagicSchoolsHandler from './2024/magicSchools'\nimport ProficienciesHandler from './2024/proficiencies'\nimport SkillsHandler from './2024/skills'\nimport SpeciesHandler from './2024/species'\nimport SubclassesHandler from './2024/subclasses'\nimport SubspeciesHandler from './2024/subspecies'\nimport TraitsHandler from './2024/traits'\nimport WeaponMasteryPropertiesHandler from './2024/weaponMasteryProperties'\nimport WeaponPropertiesHandler from './2024/weaponProperty'\n\nconst router = express.Router()\n\nrouter.get('/', function (req, res, next) {\n  index(req, res, next)\n})\n\nrouter.use('/ability-scores', AbilityScoresHandler)\nrouter.use('/alignments', AlignmentsHandler)\nrouter.use('/backgrounds', BackgroundsHandler)\nrouter.use('/conditions', ConditionsHandler)\nrouter.use('/damage-types', DamageTypesHandler)\nrouter.use('/equipment', EquipmentHandler)\nrouter.use('/equipment-categories', EquipmentCategoriesHandler)\nrouter.use('/feats', FeatsHandler)\nrouter.use('/languages', LanguagesHandler)\nrouter.use('/magic-items', MagicItemsHandler)\nrouter.use('/magic-schools', MagicSchoolsHandler)\nrouter.use('/proficiencies', ProficienciesHandler)\nrouter.use('/skills', SkillsHandler)\nrouter.use('/species', SpeciesHandler)\nrouter.use('/subclasses', SubclassesHandler)\nrouter.use('/subspecies', SubspeciesHandler)\nrouter.use('/traits', TraitsHandler)\nrouter.use('/weapon-mastery-properties', WeaponMasteryPropertiesHandler)\nrouter.use('/weapon-properties', WeaponPropertiesHandler)\n\nexport default router\n"
  },
  {
    "path": "src/routes/api/images.ts",
    "content": "import * as express from 'express'\n\nimport ImageController from '@/controllers/api/imageController'\n\nconst router = express.Router()\n\nrouter.get('/*splat', function (req, res, next) {\n  ImageController.show(req, res, next)\n})\n\nexport default router\n"
  },
  {
    "path": "src/routes/api.ts",
    "content": "import express from 'express'\n\nimport deprecatedApiController from '@/controllers/apiController'\n\nimport v2014Handler from './api/2014'\nimport v2024Handler from './api/2024'\nimport ImageHandler from './api/images'\nconst router = express.Router()\n\nrouter.use('/2014', v2014Handler)\nrouter.use('/2024', v2024Handler)\nrouter.use('/images', ImageHandler)\nrouter.get('*splat', deprecatedApiController)\n\nexport default router\n"
  },
  {
    "path": "src/schemas/schemas.ts",
    "content": "import { z } from 'zod'\n\n// --- Helper Functions ---\n/**\n * Zod transform helper to ensure the value is an array or undefined.\n * If the input is a single value, it's wrapped in an array.\n * If the input is already an array, it's returned as is.\n * If the input is nullish, undefined is returned.\n */\nconst ensureArrayOrUndefined = <T>(val: T | T[] | undefined): T[] | undefined => {\n  if (val === undefined || val === null) {\n    return undefined\n  }\n  if (Array.isArray(val)) {\n    return val\n  }\n  return [val]\n}\n\n// --- Base Schemas ---\nexport const ShowParamsSchema = z.object({\n  index: z.string().min(1)\n})\n\nexport const NameQuerySchema = z.object({\n  name: z.string().optional()\n})\n\n// --- Derived Generic Schemas ---\nexport const NameDescQuerySchema = NameQuerySchema.extend({\n  desc: z.string().optional()\n})\n\nexport const LevelParamsSchema = ShowParamsSchema.extend({\n  level: z.coerce.number().int().min(1).max(20)\n})\n\n// --- Specific Controller Schemas ---\n\n// Schemas from api/2014/spellController.ts\nexport const SpellIndexQuerySchema = NameQuerySchema.extend({\n  level: z\n    .string()\n    .regex(/^\\d+$/)\n    .or(z.array(z.string().regex(/^\\d+$/)))\n    .optional()\n    .transform(ensureArrayOrUndefined),\n  school: z.string().or(z.array(z.string())).optional().transform(ensureArrayOrUndefined)\n})\n\n// Schemas from api/2014/classController.ts\nexport const ClassLevelsQuerySchema = z.object({\n  subclass: z.string().min(1).optional()\n})\n\n// Schemas from api/2014/monsterController.ts\n// --- Helper Transformation (for MonsterIndexQuerySchema) ---\nconst transformChallengeRating = (val: string | string[] | undefined) => {\n  if (val == null || val === '' || (Array.isArray(val) && val.length === 0)) return undefined\n  // Ensure it's an array, handling both single string and array inputs\n  const arr = Array.isArray(val) ? val : [val]\n  // Flatten in case of comma-separated strings inside the array, then split\n  const flattened = arr.flatMap((item) => item.split(','))\n  // Convert to numbers and filter out NaNs\n  const numbers = flattened.map(Number).filter((item) => !isNaN(item))\n  // Return undefined if no valid numbers result, otherwise return the array\n  return numbers.length > 0 ? numbers : undefined\n}\n\nexport const MonsterIndexQuerySchema = NameQuerySchema.extend({\n  challenge_rating: z.string().or(z.string().array()).optional().transform(transformChallengeRating)\n})\n"
  },
  {
    "path": "src/server.ts",
    "content": "import 'reflect-metadata' // Must be imported first\n\nimport path from 'path'\nimport { fileURLToPath } from 'url'\n\nimport { expressMiddleware } from '@as-integrations/express5'\nimport bodyParser from 'body-parser'\nimport cors from 'cors'\nimport express from 'express'\nimport rateLimit from 'express-rate-limit'\nimport morgan from 'morgan'\nimport { buildSchema } from 'type-graphql'\n\nimport docsController from './controllers/docsController'\nimport { resolvers as resolvers2014 } from './graphql/2014/resolvers'\nimport { resolvers as resolvers2024 } from './graphql/2024/resolvers'\nimport { createApolloMiddleware } from './middleware/apolloServer'\nimport bugsnagMiddleware from './middleware/bugsnag'\nimport errorHandlerMiddleware from './middleware/errorHandler'\nimport apiRoutes from './routes/api'\n\nconst __filename = fileURLToPath(import.meta.url)\n\nconst __dirname = path.dirname(__filename)\n\nconst rateLimitWindowMs =\n  process.env.RATE_LIMIT_WINDOW_MS != null ? parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) : 1000 // Default 1 second\n\nconst rateLimitMax =\n  process.env.RATE_LIMIT_MAX != null ? parseInt(process.env.RATE_LIMIT_MAX, 10) : 50 // Default 50\n\nconst limiter = rateLimit({\n  windowMs: rateLimitWindowMs,\n  max: rateLimitMax,\n  message: `Rate limit of ${rateLimitMax} requests per ${rateLimitWindowMs / 1000} second(s) exceeded, try again later.`\n})\n\nexport default async () => {\n  const app = express()\n\n  // Middleware stuff\n  if (bugsnagMiddleware) {\n    app.use(bugsnagMiddleware.requestHandler)\n  }\n\n  app.use('/swagger', express.static(__dirname + '/swagger'))\n  app.use('/js', express.static(__dirname + '/js'))\n  app.use('/css', express.static(__dirname + '/css'))\n  app.use('/public', express.static(__dirname + '/public'))\n  app.use(morgan('short'))\n  // Enable all CORS requests\n  app.use(cors())\n\n  app.use(limiter)\n\n  console.log('Building TypeGraphQL schema...')\n  const schema2014 = await buildSchema({\n    resolvers: resolvers2014,\n    validate: { forbidUnknownValues: false }\n  })\n  const schema2024 = await buildSchema({\n    resolvers: resolvers2024,\n    validate: { forbidUnknownValues: false }\n  })\n  console.log('TypeGraphQL schema built successfully.')\n\n  console.log('Setting up Apollo GraphQL server')\n  const apolloMiddleware2024 = await createApolloMiddleware(schema2024)\n  await apolloMiddleware2024.start()\n  app.use(\n    '/graphql/2024',\n    cors<cors.CorsRequest>(),\n    bodyParser.json(),\n    expressMiddleware(apolloMiddleware2024, {\n      context: async ({ req }) => ({ token: req.headers.token })\n    })\n  )\n  const apolloMiddleware2014 = await createApolloMiddleware(schema2014)\n  await apolloMiddleware2014.start()\n  app.use(\n    '/graphql/2014',\n    cors<cors.CorsRequest>(),\n    bodyParser.json(),\n    expressMiddleware(apolloMiddleware2014, {\n      context: async ({ req }) => ({ token: req.headers.token })\n    })\n  )\n  // DEPRECATED\n  app.use(\n    '/graphql',\n    cors<cors.CorsRequest>(),\n    bodyParser.json(),\n    expressMiddleware(apolloMiddleware2014, {\n      context: async ({ req }) => ({ token: req.headers.token })\n    })\n  )\n\n  // Register routes\n  app.get('/', (req, res) => {\n    res.sendFile(path.join(__dirname, 'public/index.html'))\n  })\n  app.get('/docs', docsController)\n  app.use('/api', apiRoutes)\n\n  if (bugsnagMiddleware?.errorHandler) {\n    app.use(bugsnagMiddleware.errorHandler)\n  }\n\n  app.use(errorHandlerMiddleware)\n  return app\n}\n"
  },
  {
    "path": "src/start.ts",
    "content": "import mongoose from 'mongoose'\n\nimport createApp from './server'\nimport { mongodbUri, prewarmCache, redisClient } from './util'\n\nconst start = async () => {\n  console.log('Setting up MongoDB')\n  // Mongoose: the `strictQuery` option will be switched back to `false` by\n  // default in Mongoose 7, when we update to Mongoose 7 we can remove this.\n  mongoose.set('strictQuery', false)\n\n  await mongoose.connect(mongodbUri)\n  console.log('Database connection ready')\n\n  redisClient.on('error', (err) => console.log('Redis Client Error', err))\n\n  await redisClient.connect()\n  console.log('Redis connection ready')\n\n  console.log('Flushing Redis')\n  await redisClient.flushAll()\n\n  console.log('Prewarm Redis')\n  await prewarmCache()\n\n  console.log('Setting up Express server')\n  const app = await createApp()\n\n  console.log('Starting server...')\n  const port = process.env.PORT ?? 3000\n  app.listen(port, () => {\n    console.log(`Listening on port ${port}! 🚀`)\n  })\n}\n\nstart().catch((err) => {\n  console.error(err)\n  process.exit(1)\n})\n"
  },
  {
    "path": "src/swagger/README.md",
    "content": "# OpenAPI for the DND API\n\nThe `/swagger` directory contains an OpenAPI 3.0 definition for the DND API.\n\nWe use [swagger-cli](https://github.com/APIDevTools/swagger-cli) to validate and bundle\nour OpenAPI definition, and [RapiDoc](https://mrin9.github.io/RapiDoc/index.html) as the documentation viewer.\n\n**_Note:_** there are currently a handful of small inconsistencies between the documentation and actual API response. If you come across any please let us know!\n\n**_Possible Future Improvements (PRs Encouraged :)_**\n\n- [ ] standardize schema object naming (mostly cleanup the /schemas directory)\n- [ ] validate schemas against models or actual api responses\n- [ ] validate schema and field descriptions are accurate\n- [ ] reorganize tag ordering\n- [ ] add tag descriptions\n- [ ] enumerate the `class.class_specific` field\n- [ ] give user option to change render style and schema style (rapidoc)\n- [ ] generate pieces of documentation based on source code e.g., generate OpenAPI `SchemaObject` from a TypeScript `type` definition\n- [ ] code snippet examples in various languages\n- [ ] ...anything else you want!\n\n## Background\n\n### What is the OpenAPI Specification?\n\n> The OpenAPI Specification (OAS) defines a standard, programming language-agnostic interface description for HTTP APIs, which allows both humans and computers to discover and understand the capabilities of a service without requiring access to source code, additional documentation, or inspection of network traffic. When properly defined via OpenAPI, a consumer can understand and interact with the remote service with a minimal amount of implementation logic. Similar to what interface descriptions have done for lower-level programming, the OpenAPI Specification removes guesswork in calling a service.[^openapi]\n\n[^openapi]: https://github.com/OAI/OpenAPI-Specification/\n\n### What is Swagger?\n\n> Swagger is a set of open-source tools built around the OpenAPI Specification that can help you design, build, document and consume REST APIs.[^swagger]\n\n[^swagger]: https://swagger.io/docs/specification/about/\n\n### Demo\n\nA valid OpenAPI definition gives us a bunch of options when it comes to surfacing docs to end users.\n\n![example!](./assets/demo.gif 'example')\n\n## Documenting an Endpoint\n\nWe need 3 pieces to document an endpoint under the OpenAPI spec:\n\n- [PathItemObject][pathobj]: Describes the operations available on a single path. Defines the shape of parameters, requests, and responses for a particular endpoint.\n- [Parameter Object][paramobj]: Describes a single operation parameter. The expected format, acceptable values, and whether the parameter is required or optional.\n- [Schema Object][schemaobj]: Describes the definition of an input or output data types. These types can be objects, but also primitives and arrays.\n\n[schemaobj]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schemaObject\n[pathobj]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#pathItemObject\n[paramobj]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameterObject\n\n## File Organization\n\n> An OpenAPI document MAY be made up of a single document or be divided into multiple, connected parts at the discretion of the user.[^oas_org]\n\n[^oas_org]: An OpenAPI document is a document (or set of documents) that defines or describes an API. An OpenAPI definition uses and conforms to the OpenAPI Specification. An OpenAPI document that conforms to the OpenAPI Specification is itself a JSON object, which may be represented either in JSON or YAML format. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#document-structure\n\nThe root of our OpenAPI definition lives in `swagger.yml`. This file contains general information about the API, endpoint definitions, and definitions of reusable components. Reference Objects[^oas_ref] are used to allow components to reference each other.\n\n[^oas_ref]: A simple object to allow referencing other components in the specification, internally and externally. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#referenceObject\n\nFor reusability and readability, definitions are split into 3 directories.\n\n- `/schemas`\n  - Each `.yml` file contains definitions of one or more `SchemaObject`. Each schema more or less corresponds to one of the models in the [`src/models` directory](https://github.com/5e-bits/5e-srd-api/tree/main/src/models). We use these schemas to describe the structure of response bodies.\n- `/paths`\n  - Each `.yml` file contains definitions of one or more `PathItemObject`, where each of those objects defines an operation on an endpoint. Each file more or less corresponds to a controller in the [`src/controllers/api` directory](https://github.com/5e-bits/5e-srd-api/tree/main/src/controllers/api). These objects also include the example response shown in the endpoint documentation.\n- `/parameters`\n  - Contains definitions of reusable path and query parameters.\n\nEach of those directories contains a file named `combined.yml` consisting of named references to each of the objects defined in sibling `.yml` files in that directory. The `combined.yml` files provides a single source we can reference from other components. By only referencing objects from the `combined.yml` files, we avoid any problems with circular references.\n\n## Developing Locally\n\nThere's many possible ways to make changes and view them locally, I'll describe my personal setup and workflow here.\n\n- local copy of the [database](https://github.com/5e-bits/5e-database) running in a custom built docker container on a machine on my local network\n- [`redis`](https://redis.io/) running on my laptop\n- local copy of the [api](https://github.com/5e-bits/5e-srd-api) running against my local database\n  - I start this by running `MONGODB_URI=mongodb://<LOCAL_IP>/5e-database npm start` where `LOCAL_IP` is the ip address of the machine running the database docker container\n- [Swagger Viewer](https://marketplace.visualstudio.com/items?itemName=Arjun.swagger-viewer) extension for VSCode\n  - be sure to trigger the \"Preview Swagger\" command from the `swagger.yml` file\n\n### Useful Commands\n\nFrom the root of the project directory two `npm` commands are available related to these docs.\n\n`npm run validate-swagger`\n\n- checks that the OpenAPI definition in `swagger/swagger.yml` is valid\n\n`npm run bundle-swagger`\n\n- bundles the OpenAPI definition in `swagger/swagger.json` with all the associated referenced files, and writes the file to the `swagger/dist` directory\n\n## Postman\n\n[Postman](https://learning.postman.com/docs/getting-started/introduction/) is a platform for building and using APIs, it provides a user friendly GUI for creating and testing HTTP requests, and is free for personal use. We don't use Postman to build this API, but it can be a useful tool for testing and exploration. You can generate a Postman collection based on the OpenAPI definition for the API via the `npm run gen-postman` task, and then [import that collection into Postman](https://learning.postman.com/docs/getting-started/importing-and-exporting-data/#importing-data-into-postman) to use it.\n\nUnder the hood the `gen-postman` task uses [`portman`](https://github.com/apideck-libraries/portman), based on the configuration defined in `portman-cli.json`. There's a number of configuration options that affect how the collection is generated, you can experiment with these options either by changing your local copy of that config file and running the `gen-postman` task, or by executing `portman` from the command line with different options.\n\nFor example, the CLI command equivalent to the config file would be:\n\n```bash\nportman -l src/swagger/dist/openapi.yml -o collection.postman.json -t\n```\n"
  },
  {
    "path": "src/swagger/parameters/2014/combined.yml",
    "content": "ability-score-index:\n  $ref: './path/ability-scores.yml'\nalignment-index:\n  $ref: './path/alignments.yml'\nlanguage-index:\n  $ref: './path/languages.yml'\nproficiency-index:\n  $ref: './path/proficiencies.yml'\nskill-index:\n  $ref: './path/skills.yml'\nclass-index:\n  $ref: './path/classes.yml#/class-index'\nbackground-index:\n  $ref: './path/backgrounds.yml'\nweapon-property-index:\n  $ref: './path/weapon-properties.yml'\nclass-level:\n  $ref: './path/classes.yml#/class-level'\nspell-level:\n  $ref: './path/classes.yml#/spell-level'\ncondition-index:\n  $ref: './path/conditions.yml'\ndamage-type-index:\n  $ref: './path/damage-types.yml'\nmagic-school-index:\n  $ref: './path/magic-schools.yml'\nequipment-index:\n  $ref: './path/equipment.yml'\nfeature-index:\n  $ref: './path/features.yml'\nrule-index:\n  $ref: './path/rules.yml'\nrule-section-index:\n  $ref: './path/rule-sections.yml'\nrace-index:\n  $ref: './path/races.yml'\nsubclass-index:\n  $ref: './path/subclasses.yml'\nsubrace-index:\n  $ref: './path/subraces.yml'\ntrait-index:\n  $ref: './path/traits.yml'\nmonster-index:\n  $ref: './path/monsters.yml'\nspell-index:\n  $ref: './path/spells.yml'\nlevel-filter:\n  $ref: './query/spells.yml#/level-filter'\nschool-filter:\n  $ref: './query/spells.yml#/school-filter'\nchallenge-rating-filter:\n  $ref: './query/monsters.yml#/challenge-rating-filter'\nlevels-subclass-filter:\n  $ref: './query/classes.yml#/levels-subclass-filter'\nbase-endpoint-index:\n  $ref: './path/common.yml'\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/ability-scores.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the ability score to get.\nschema:\n  type: string\n  enum: [cha, con, dex, int, str, wis]\n  example: cha\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/alignments.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the alignment to get.\nschema:\n  type: string\n  enum:\n    - chaotic-neutral\n    - chaotic-evil\n    - chaotic-good\n    - lawful-neutral\n    - lawful-evil\n    - lawful-good\n    - neutral\n    - neutral-evil\n    - neutral-good\n  example: chaotic-neutral\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/backgrounds.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the background to get.\nschema:\n  type: string\n  enum: [acolyte]\n  example: acolyte\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/classes.yml",
    "content": "class-index:\n  name: index\n  in: path\n  required: true\n  description: |\n    The `index` of the class to get.\n  schema:\n    type: string\n    enum:\n      - barbarian\n      - bard\n      - cleric\n      - druid\n      - fighter\n      - monk\n      - paladin\n      - ranger\n      - rogue\n      - sorcerer\n      - warlock\n      - wizard\n    example: paladin\nclass-level:\n  name: class_level\n  in: path\n  required: true\n  schema:\n    type: number\n    minimum: 0\n    maximum: 20\n    example: 3\nspell-level:\n  name: spell_level\n  in: path\n  required: true\n  schema:\n    type: number\n    minimum: 1\n    maximum: 9\n    example: 4\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/common.yml",
    "content": "name: endpoint\nin: path\nrequired: true\nschema:\n  type: string\n  enum:\n    - ability-scores\n    - alignments\n    - backgrounds\n    - classes\n    - conditions\n    - damage-types\n    - equipment\n    - equipment-categories\n    - feats\n    - features\n    - languages\n    - magic-items\n    - magic-schools\n    - monsters\n    - proficiencies\n    - races\n    - rule-sections\n    - rules\n    - skills\n    - spells\n    - subclasses\n    - subraces\n    - traits\n    - weapon-properties\n  example: ability-scores\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/conditions.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the condition to get.\nschema:\n  type: string\n  enum:\n    - blinded\n    - charmed\n    - deafened\n    - exhaustion\n    - frightened\n    - grappled\n    - incapacitated\n    - invisible\n    - paralyzed\n    - petrified\n    - poisoned\n    - prone\n    - restrained\n    - stunned\n    - unconscious\n  example: blinded\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/damage-types.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the damage type to get.\nschema:\n  type: string\n  enum:\n    - acid\n    - bludgeoning\n    - cold\n    - fire\n    - force\n    - lightning\n    - necrotic\n    - piercing\n    - poison\n    - psychic\n    - radiant\n    - slashing\n    - thunder\n  example: acid\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/equipment.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the equipment to get.\n\n  Available values can be found in the [`ResourceList`](#get-/api/2014/-endpoint-) for `equipment`.\nschema:\n  type: string\n  example: club\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/features.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the feature to get.\n\n  Available values can be found in the [`ResourceList`](#get-/api/2014/-endpoint-) for `features`.\nschema:\n  type: string\nexample: action-surge-1-use\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/languages.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the language to get.\nschema:\n  type: string\n  enum:\n    - abyssal\n    - celestial\n    - common\n    - deep-speech\n    - draconic\n    - dwarvish\n    - elvish\n    - giant\n    - gnomish\n    - goblin\n    - halfling\n    - infernal\n    - orc\n    - primordial\n    - sylvan\n    - undercommon\n  example: abyssal\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/magic-schools.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the magic school to get.\nschema:\n  type: string\n  enum:\n    - abjuration\n    - conjuration\n    - divination\n    - enchantment\n    - evocation\n    - illusion\n    - necromancy\n    - transmutation\n  example: abjuration\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/monsters.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the `Monster` to get.\nschema:\n  type: string\n  example: aboleth\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/proficiencies.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the proficiency to get.\n\n  Available values can be found in the [`ResourceList`](#get-/api/2014/-endpoint-) for `proficiencies`.\nschema:\n  type: string\n  example: medium-armor\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/races.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the race to get.\nschema:\n  type: string\n  enum:\n    - dragonborn\n    - dwarf\n    - elf\n    - gnome\n    - half-elf\n    - half-orc\n    - halfling\n    - human\n    - tiefling\n  example: elf\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/rule-sections.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the rule section to get.\nschema:\n  type: string\n  enum:\n    - ability-checks\n    - ability-scores-and-modifiers\n    - actions-in-combat\n    - activating-an-item\n    - advantage-and-disadvantage\n    - attunement\n    - between-adventures\n    - casting-a-spell\n    - cover\n    - damage-and-healing\n    - diseases\n    - fantasy-historical-pantheons\n    - madness\n    - making-an-attack\n    - mounted-combat\n    - movement\n    - movement-and-position\n    - objects\n    - poisons\n    - proficiency-bonus\n    - resting\n    - saving-throws\n    - sentient-magic-items\n    - standard-exchange-rates\n    - the-environment\n    - the-order-of-combat\n    - the-planes-of-existence\n    - time\n    - traps\n    - underwater-combat\n    - using-each-ability\n    - wearing-and-wielding-items\n    - what-is-a-spell\n  example: traps\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/rules.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the rule to get.\nschema:\n  type: string\n  enum:\n    - adventuring\n    - appendix\n    - combat\n    - equipment\n    - spellcasting\n    - using-ability-scores\n  example: adventuring\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/skills.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the skill to get.\nschema:\n  type: string\n  enum:\n    - acrobatics\n    - animal-handling\n    - arcana\n    - athletics\n    - deception\n    - history\n    - insight\n    - intimidation\n    - investigation\n    - medicine\n    - nature\n    - perception\n    - performance\n    - persuasion\n    - religion\n    - sleight-of-hand\n    - stealth\n    - survival\n  example: nature\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/spells.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the `Spell` to get.\n\n  Available values can be found in the [`ResourceList`](#get-/api/2014/-endpoint-) for `spells`.\nschema:\n  type: string\n  example: sacred-flame\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/subclasses.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the subclass to get.\nschema:\n  type: string\n  enum:\n    - berserker\n    - champion\n    - devotion\n    - draconic\n    - evocation\n    - fiend\n    - hunter\n    - land\n    - life\n    - lore\n    - open-hand\n    - thief\n  example: fiend\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/subraces.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the subrace to get.\nschema:\n  type: string\n  enum:\n    - high-elf\n    - hill-dwarf\n    - lightfoot-halfling\n    - rock-gnome\n  example: hill-dwarf\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/traits.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: The `index` of the `Trait` to get.\nschema:\n  type: string\n  enum:\n    - artificers-lore\n    - brave\n    - breath-weapon\n    - damage-resistance\n    - darkvision\n    - draconic-ancestry\n    - draconic-ancestry-black\n    - draconic-ancestry-blue\n    - draconic-ancestry-brass\n    - draconic-ancestry-bronze\n    - draconic-ancestry-copper\n    - draconic-ancestry-gold\n    - draconic-ancestry-green\n    - draconic-ancestry-red\n    - draconic-ancestry-silver\n    - draconic-ancestry-white\n    - dwarven-combat-training\n    - dwarven-resilience\n    - dwarven-toughness\n    - elf-weapon-training\n    - extra-language\n    - fey-ancestry\n    - gnome-cunning\n    - halfling-nimbleness\n    - hellish-resistance\n    - high-elf-cantrip\n    - infernal-legacy\n    - keen-senses\n    - lucky\n    - menacing\n    - naturally-stealthy\n    - relentless-endurance\n    - savage-attacks\n    - skill-versatility\n    - stonecunning\n    - tinker\n    - tool-proficiency\n    - trance\n  example: trance\n"
  },
  {
    "path": "src/swagger/parameters/2014/path/weapon-properties.yml",
    "content": "name: index\nin: path\nrequired: true\ndescription: |\n  The `index` of the weapon property to get.\nschema:\n  type: string\n  enum:\n    - ammunition\n    - finesse\n    - heavy\n    - light\n    - loading\n    - monk\n    - reach\n    - special\n    - thrown\n    - two-handed\n    - versatile\n  example: ammunition\n"
  },
  {
    "path": "src/swagger/parameters/2014/query/classes.yml",
    "content": "levels-subclass-filter:\n  name: subclass\n  in: query\n  required: false\n  description: Adds subclasses for class to the response\n  schema:\n    type: string\n  examples:\n    single-value:\n      value: berserker\n    partial-value:\n      value: ber\n"
  },
  {
    "path": "src/swagger/parameters/2014/query/monsters.yml",
    "content": "challenge-rating-filter:\n  name: challenge_rating\n  in: query\n  required: false\n  description: The challenge rating or ratings to filter on.\n  schema:\n    type: array\n    items:\n      type: number\n  examples:\n    single-value:\n      value: [1]\n    multiple-value:\n      value: [1, 2]\n    multiple-value-with-float:\n      value: [2, 0.25]\n"
  },
  {
    "path": "src/swagger/parameters/2014/query/spells.yml",
    "content": "level-filter:\n  name: level\n  in: query\n  required: false\n  description: The level or levels to filter on.\n  schema:\n    type: array\n    items:\n      type: integer\n  examples:\n    single-value:\n      value: [1]\n    multiple-value:\n      value: [1, 2]\nschool-filter:\n  name: school\n  in: query\n  required: false\n  description: The magic school or schools to filter on.\n  schema:\n    type: array\n    items:\n      type: string\n  examples:\n    single-value:\n      value: [illusion]\n    multiple-value:\n      value: [evocation, illusion]\n    partial-value:\n      value: [illu]\n"
  },
  {
    "path": "src/swagger/parameters/2024/.keepme",
    "content": ""
  },
  {
    "path": "src/swagger/paths/2014/ability-scores.yml",
    "content": "get:\n  summary: Get an ability score by index.\n  description: |\n    # Ability Score\n\n    Represents one of the six abilities that describes a creature's physical and mental characteristics. The three main rolls of the game - the ability check, the saving throw, and the attack roll - rely on the ability scores. [[SRD p76](https://media.wizards.com/2016/downloads/DND/SRD-OGL_V5.1.pdf#page=76)]\n  tags:\n    - Character Data\n  parameters:\n    - $ref: '../../parameters/2014/combined.yml#/ability-score-index'\n  responses:\n    '200':\n      description: OK\n      content:\n        application/json:\n          schema:\n            $ref: '../../schemas/2014/combined.yml#/AbilityScore'\n          example:\n            index: 'cha'\n            name: 'CHA'\n            url: '/api/2014/ability-scores/cha'\n            desc:\n              [\n                'Charisma measures your ability to interact effectively with others. It includes such factors as confidence and eloquence, and it can represent a charming or commanding personality.',\n                'A Charisma check might arise when you try to influence or entertain others, when you try to make an impression or tell a convincing lie, or when you are navigating a tricky social situation. The Deception, Intimidation, Performance, and Persuasion skills reflect aptitude in certain kinds of Charisma checks.'\n              ]\n            full_name: 'Charisma'\n            skills:\n              - index: 'deception'\n                name: 'Deception'\n                url: '/api/2014/skills/deception'\n              - index: 'intimidation'\n                name: 'Intimidation'\n                url: '/api/2014/skills/intimidation'\n              - index: 'performance'\n                name: 'Performance'\n                url: '/api/2014/skills/performance'\n              - index: 'persuasion'\n                name: 'Persuasion'\n                url: '/api/2014/skills/persuasion'\n"
  },
  {
    "path": "src/swagger/paths/2014/alignments.yml",
    "content": "get:\n  summary: Get an alignment by index.\n  description: |\n    # Alignment\n\n    A typical creature in the game world has an alignment, which broadly describes its moral and personal attitudes. Alignment is a combination of two factors: one identifies morality (good, evil, or neutral), and the other describes attitudes toward society and order (lawful, chaotic, or neutral). Thus, nine distinct alignments define the possible combinations.[[SRD p58](https://media.wizards.com/2016/downloads/DND/SRD-OGL_V5.1.pdf#page=58)]\n  tags:\n    - Character Data\n  parameters:\n    - $ref: '../../parameters/2014/combined.yml#/alignment-index'\n  responses:\n    '200':\n      description: 'OK'\n      content:\n        application/json:\n          schema:\n            $ref: '../../schemas/2014/combined.yml#/Alignment'\n          example:\n            index: chaotic-neutral\n            name: Chaotic Neutral\n            url: '/api/2014/alignments/chaotic-neutral'\n            desc: Chaotic neutral (CN) creatures follow their whims, holding their personal freedom above all else. Many barbarians and rogues, and some bards, are chaotic neutral.\n            abbreviation: CN\n"
  },
  {
    "path": "src/swagger/paths/2014/backgrounds.yml",
    "content": "get:\n  summary: Get a background by index.\n  description: |\n    # Background\n\n    Every story has a beginning. Your character's background reveals where you came from, how you became an adventurer, and your place in the world. Choosing a background provides you with important story cues about your character's identity. [[SRD p60](https://media.wizards.com/2016/downloads/DND/SRD-OGL_V5.1.pdf#page=60)]\n\n    _Note:_ acolyte is the only background included in the SRD.\n  tags:\n    - Character Data\n  parameters:\n    - $ref: '../../parameters/2014/combined.yml#/background-index'\n  responses:\n    '200':\n      description: OK\n      content:\n        application/json:\n          schema:\n            $ref: '../../schemas/2014/combined.yml#/Background'\n          example:\n            index: acolyte\n            name: Acolyte\n            starting_proficiencies:\n              - index: skill-insight\n                name: 'Skill: Insight'\n                url: '/api/2014/proficiencies/skill-insight'\n              - index: skill-religion\n                name: 'Skill: Religion'\n                url: '/api/2014/proficiencies/skill-religion'\n            language_options:\n              choose: 2\n              type: languages\n              from:\n                option_set_type: resource_list\n                resource_list_url: '/api/2014/languages'\n            starting_equipment:\n              - equipment:\n                  index: clothes-common\n                  name: Clothes, common\n                  url: '/api/2014/equipment/clothes-common'\n                quantity: 1\n              - equipment:\n                  index: pouch\n                  name: Pouch\n                  url: '/api/2014/equipment/pouch'\n                quantity: 1\n            starting_equipment_options:\n              - choose: 1\n                type: equipment\n                from:\n                  option_set_type: equipment_category\n                  equipment_category:\n                    index: holy-symbols\n                    name: Holy Symbols\n                    url: '/api/2014/equipment-categories/holy-symbols'\n            feature:\n              name: Shelter of the Faithful\n              desc:\n                - As an acolyte, you command the respect of those who share your faith, and you\n                  can perform the religious ceremonies of your deity. You and your adventuring companions\n                  can expect to receive free healing and care at a temple, shrine, or other established\n                  presence of your faith, though you must provide any material components needed\n                  for spells. Those who share your religion will support you (but only you) at a\n                  modest lifestyle.\n                - You might also have ties to a specific temple dedicated to your chosen deity or\n                  pantheon, and you have a residence there. This could be the temple where you used\n                  to serve, if you remain on good terms with it, or a temple where you have found\n                  a new home. While near your temple, you can call upon the priests for assistance,\n                  provided the assistance you ask for is not hazardous and you remain in good standing\n                  with your temple.\n            personality_traits:\n              choose: 2\n              type: personality_traits\n              from:\n                option_set_type: options_array\n                options:\n                  - option_type: string\n                    string: I idolize a particular hero of my faith, and constantly refer to that person's deeds and example.\n                  - option_type: string\n                    string: I can find common ground between the fiercest enemies, empathizing with them and always working toward peace.\n                  - option_type: string\n                    string: I see omens in every event and action. The gods try to speak to us, we just need to listen.\n                  - option_type: string\n                    string: Nothing can shake my optimistic attitude.\n                  - option_type: string\n                    string: I quote (or misquote) sacred texts and proverbs in almost every situation.\n                  - option_type: string\n                    string: I am tolerant (or intolerant) of other faiths and respect (or condemn) the worship of other gods.\n                  - option_type: string\n                    string: I've enjoyed fine food, drink, and high society among my temple's elite. Rough living grates on me.\n                  - option_type: string\n                    string: I've spent so long in the temple that I have little practical experience dealing with people in the outside world.\n            ideals:\n              choose: 1\n              type: ideals\n              from:\n                option_set_type: options_array\n                options:\n                  - option_type: ideal\n                    desc: Tradition. The ancient traditions of worship and sacrifice must be preserved and upheld.\n                    alignments:\n                      - index: lawful-good\n                        name: Lawful Good\n                        url: '/api/2014/alignments/lawful-good'\n                      - index: lawful-neutral\n                        name: Lawful Neutral\n                        url: '/api/2014/alignments/lawful-neutral'\n                      - index: lawful-evil\n                        name: Lawful Evil\n                        url: '/api/2014/alignments/lawful-evil'\n                  - option_type: ideal\n                    desc: Charity. I always try to help those in need, no matter what the personal cost.\n                    alignments:\n                      - index: lawful-good\n                        name: Lawful Good\n                        url: '/api/2014/alignments/lawful-good'\n                      - index: neutral-good\n                        name: Neutral Good\n                        url: '/api/2014/alignments/neutral-good'\n                      - index: chaotic-good\n                        name: Chaotic Good\n                        url: '/api/2014/alignments/chaotic-good'\n                  - option_type: ideal\n                    desc: Change. We must help bring about the changes the gods are constantly working in the world.\n                    alignments:\n                      - index: chaotic-good\n                        name: Chaotic Good\n                        url: '/api/2014/alignments/chaotic-good'\n                      - index: chaotic-neutral\n                        name: Chaotic Neutral\n                        url: '/api/2014/alignments/chaotic-neutral'\n                      - index: chaotic-evil\n                        name: Chaotic Evil\n                        url: '/api/2014/alignments/chaotic-evil'\n                  - option_type: ideal\n                    desc: Power. I hope to one day rise to the top of my faith's religious hierarchy.\n                    alignments:\n                      - index: lawful-good\n                        name: Lawful Good\n                        url: '/api/2014/alignments/lawful-good'\n                      - index: lawful-neutral\n                        name: Lawful Neutral\n                        url: '/api/2014/alignments/lawful-neutral'\n                      - index: lawful-evil\n                        name: Lawful Evil\n                        url: '/api/2014/alignments/lawful-evil'\n                  - option_type: ideal\n                    desc: Faith. I trust that my deity will guide my actions. I have faith that if I work hard, things will go well.\n                    alignments:\n                      - index: lawful-good\n                        name: Lawful Good\n                        url: '/api/2014/alignments/lawful-good'\n                      - index: lawful-neutral\n                        name: Lawful Neutral\n                        url: '/api/2014/alignments/lawful-neutral'\n                      - index: lawful-evil\n                        name: Lawful Evil\n                        url: '/api/2014/alignments/lawful-evil'\n                  - option_type: ideal\n                    desc: Aspiration. I seek to prove myself worthy of my god's favor by matching my actions against his or her teachings.\n                    alignments:\n                      - index: lawful-good\n                        name: Lawful Good\n                        url: '/api/2014/alignments/lawful-good'\n                      - index: neutral-good\n                        name: Neutral Good\n                        url: '/api/2014/alignments/neutral-good'\n                      - index: chaotic-good\n                        name: Chaotic Good\n                        url: '/api/2014/alignments/chaotic-good'\n                      - index: lawful-neutral\n                        name: Lawful Neutral\n                        url: '/api/2014/alignments/lawful-neutral'\n                      - index: neutral\n                        name: Neutral\n                        url: '/api/2014/alignments/neutral'\n                      - index: chaotic-neutral\n                        name: Chaotic Neutral\n                        url: '/api/2014/alignments/chaotic-neutral'\n                      - index: lawful-evil\n                        name: Lawful Evil\n                        url: '/api/2014/alignments/lawful-evil'\n                      - index: neutral-evil\n                        name: Neutral Evil\n                        url: '/api/2014/alignments/neutral-evil'\n                      - index: chaotic-evil\n                        name: Chaotic Evil\n                        url: '/api/2014/alignments/chaotic-evil'\n            bonds:\n              choose: 1\n              type: bonds\n              from:\n                option_set_type: options_array\n                options:\n                  - option_type: string\n                    string: I would die to recover an ancient relic of my faith that was lost long ago.\n                  - option_type: string\n                    string: I will someday get revenge on the corrupt temple hierarchy who branded me a heretic.\n                  - option_type: string\n                    string: I owe my life to the priest who took me in when my parents died.\n                  - option_type: string\n                    string: Everything I do is for the common people.\n                  - option_type: string\n                    string: I will do anything to protect the temple where I served.\n                  - option_type: string\n                    string: I seek to preserve a sacred text that my enemies consider heretical and seek to destroy.\n            flaws:\n              choose: 1\n              type: flaws\n              from:\n                option_set_type: options_array\n                options:\n                  - option_type: string\n                    string: I judge others harshly, and myself even more severely.\n                  - option_type: string\n                    string: I put too much trust in those who wield power within my temple's hierarchy.\n                  - option_type: string\n                    string: My piety sometimes leads me to blindly trust those that profess faith in my god.\n                  - option_type: string\n                    string: I am inflexible in my thinking.\n                  - option_type: string\n                    string: I am suspicious of strangers and expect the worst of them.\n                  - option_type: string\n                    string: Once I pick a goal, I become obsessed with it to the detriment of everything else in my life.\n            url: '/api/2014/backgrounds/acolyte'\n"
  },
  {
    "path": "src/swagger/paths/2014/classes.yml",
    "content": "# /api/2014/classes/{indexParam}\nclass-path:\n  get:\n    summary: Get a class by index.\n    description: |\n      # Class\n\n      A character class is a fundamental part of the identity and nature of\n      characters in the Dungeons & Dragons role-playing game. A character's\n      capabilities, strengths, and weaknesses are largely defined by its class.\n      A character's class affects a character's available skills and abilities. [[SRD p8-55](https://media.wizards.com/2016/downloads/DND/SRD-OGL_V5.1.pdf#page=8)]\n    tags:\n      - Class\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/class-index'\n    responses:\n      '200':\n        description: OK\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/Class'\n            example:\n              class_levels: '/api/2014/classes/barbarian/levels'\n              hit_die: 12\n              index: barbarian\n              multi_classing:\n                prerequisites:\n                  - ability_score:\n                      index: str\n                      name: STR\n                      url: '/api/2014/ability-scores/str'\n                    minimum_score: 13\n                proficiencies:\n                  - index: shields\n                    name: Shields\n                    url: '/api/2014/proficiencies/shields'\n                  - index: simple-weapons\n                    name: Simple Weapons\n                    url: '/api/2014/proficiencies/simple-weapons'\n                  - index: martial-weapons\n                    name: Martial Weapons\n                    url: '/api/2014/proficiencies/martial-weapons'\n                proficiency_choices: []\n              name: Barbarian\n              proficiencies:\n                - index: light-armor\n                  name: Light Armor\n                  url: '/api/2014/proficiencies/light-armor'\n                - index: medium-armor\n                  name: Medium Armor\n                  url: '/api/2014/proficiencies/medium-armor'\n                - index: shields\n                  name: Shields\n                  url: '/api/2014/proficiencies/shields'\n                - index: simple-weapons\n                  name: Simple Weapons\n                  url: '/api/2014/proficiencies/simple-weapons'\n                - index: martial-weapons\n                  name: Martial Weapons\n                  url: '/api/2014/proficiencies/martial-weapons'\n              proficiency_choices:\n                - desc: Choose two from Animal Handling, Athletics, Intimidation, Nature, Perception, and Survival\n                  choose: 2\n                  type: proficiencies\n                  from:\n                    option_set_type: options_array\n                    options:\n                      - option_type: reference\n                        item:\n                          index: skill-animal-handling\n                          name: 'Skill: Animal Handling'\n                          url: '/api/2014/proficiencies/skill-animal-handling'\n                      - option_type: reference\n                        item:\n                          index: skill-athletics\n                          name: 'Skill: Athletics'\n                          url: '/api/2014/proficiencies/skill-athletics'\n                      - option_type: reference\n                        item:\n                          index: skill-intimidation\n                          name: 'Skill: Intimidation'\n                          url: '/api/2014/proficiencies/skill-intimidation'\n                      - option_type: reference\n                        item:\n                          index: skill-nature\n                          name: 'Skill: Nature'\n                          url: '/api/2014/proficiencies/skill-nature'\n                      - option_type: reference\n                        item:\n                          index: skill-perception\n                          name: 'Skill: Perception'\n                          url: '/api/2014/proficiencies/skill-perception'\n                      - option_type: reference\n                        item:\n                          index: skill-survival\n                          name: 'Skill: Survival'\n                          url: '/api/2014/proficiencies/skill-survival'\n              saving_throws:\n                - index: str\n                  name: STR\n                  url: '/api/2014/ability-scores/str'\n                - index: con\n                  name: CON\n                  url: '/api/2014/ability-scores/con'\n              starting_equipment:\n                - equipment:\n                    index: explorers-pack\n                    name: Explorer's Pack\n                    url: '/api/2014/equipment/explorers-pack'\n                  quantity: 1\n                - equipment:\n                    index: javelin\n                    name: Javelin\n                    url: '/api/2014/equipment/javelin'\n                  quantity: 4\n              starting_equipment_options:\n                - desc: (a) a greataxe or (b) any martial melee weapon\n                  choose: 1\n                  type: equipment\n                  from:\n                    option_set_type: options_array\n                    options:\n                      - option_type: counted_reference\n                        count: 1\n                        of:\n                          index: greataxe\n                          name: Greataxe\n                          url: '/api/2014/equipment/greataxe'\n                      - option_type: choice\n                        choice:\n                          desc: any martial melee weapon\n                          choose: 1\n                          type: equipment\n                          from:\n                            option_set_type: equipment_category\n                            equipment_category:\n                              index: martial-melee-weapons\n                              name: Martial Melee Weapons\n                              url: '/api/2014/equipment-categories/martial-melee-weapons'\n                - desc: (a) two handaxes or (b) any simple weapon\n                  choose: 1\n                  type: equipment\n                  from:\n                    option_set_type: options_array\n                    options:\n                      - option_type: counted_reference\n                        count: 2\n                        of:\n                          index: handaxe\n                          name: Handaxe\n                          url: '/api/2014/equipment/handaxe'\n                      - option_type: choice\n                        choice:\n                          desc: any simple weapon\n                          choose: 1\n                          type: equipment\n                          from:\n                            option_set_type: equipment_category\n                            equipment_category:\n                              index: simple-weapons\n                              name: Simple Weapons\n                              url: '/api/2014/equipment-categories/simple-weapons'\n              subclasses:\n                - index: berserker\n                  name: Berserker\n                  url: '/api/2014/subclasses/berserker'\n              url: '/api/2014/classes/barbarian'\n# /api/2014/classes/{indexParam}/subclasses\nclass-subclass-path:\n  get:\n    summary: Get subclasses available for a class.\n    tags:\n      - Class Resource Lists\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/class-index'\n    responses:\n      '200':\n        description: OK\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/APIReferenceList'\n            example:\n              count: 1\n              results:\n                - index: berserker\n                  name: Berserker\n                  url: '/api/2014/subclasses/berserker'\n#/api/2014/classes/{indexParam}/spells\nclass-spells-path:\n  get:\n    summary: Get spells available for a class.\n    tags:\n      - Class Resource Lists\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/class-index'\n      - $ref: '../../parameters/2014/combined.yml#/level-filter'\n    responses:\n      '200':\n        description: OK\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/ClassSpellList'\n            example:\n              count: 2\n              results:\n                - index: power-word-kill\n                  name: Power Word Kill\n                  url: '/api/2014/spells/power-word-kill'\n                  level: 9\n                - index: true-polymorph\n                  name: True Polymorph\n                  url: '/api/2014/spells/true-polymorph'\n                  level: 9\n# /api/2014/classes/{index}/spellcasting\nclass-spellcasting-path:\n  get:\n    summary: Get spellcasting info for a class.\n    tags:\n      - Class\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/class-index'\n    responses:\n      '200':\n        description: OK\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/Spellcasting'\n            example:\n              level: 1\n              spellcasting_ability:\n                index: cha\n                name: CHA\n                url: '/api/2014/ability-scores/cha'\n              info:\n                - name: Cantrips\n                  desc:\n                    - You know two cantrips of your choice from the bard spell list. You learn additional\n                      bard cantrips of your choice at higher levels, as shown in the Cantrips Known\n                      column of the Bard table.\n                - name: Spell Slots\n                  desc:\n                    - The Bard table shows how many spell slots you have to cast your spells of 1st\n                      level and higher. To cast one of these spells, you must expend a slot of the spell's\n                      level or higher. You regain all expended spell slots when you finish a long rest.\n                    - For example, if you know the 1st-level spell cure wounds and have a 1st-level\n                      and a 2nd-level spell slot available, you can cast cure wounds using either slot.\n                - name: Spells Known of 1st Level and Higher\n                  desc:\n                    - You know four 1st-level spells of your choice from the bard spell list.\n                    - The Spells Known column of the Bard table shows when you learn more bard spells\n                      of your choice.\n                    - Each of these spells must be of a level for which you have spell slots, as shown\n                      on the table. For instance, when you reach 3rd level in this class, you can learn\n                      one new spell of 1st or 2nd level.\n                    - Additionally, when you gain a level in this class, you can choose one of the bard\n                      spells you know and replace it with another spell from the bard spell list, which\n                      also must be of a level for which you have spell slots.\n                - name: Spellcasting Ability\n                  desc:\n                    - Charisma is your spellcasting ability for your bard spells. Your magic comes from\n                      the heart and soul you pour into the performance of your music or oration. You\n                      use your Charisma whenever a spell refers to your spellcasting ability. In addition,\n                      you use your Charisma modifier when setting the saving throw DC for a bard spell\n                      you cast and when making an attack roll with one.\n                    - Spell save DC = 8 + your proficiency bonus + your Charisma modifier.\n                    - Spell attack modifier = your proficiency bonus + your Charisma modifier.\n                - name: Ritual Casting\n                  desc:\n                    - You can cast any bard spell you know as a ritual if that spell has the ritual\n                      tag.\n                - name: Spellcasting Focus\n                  desc:\n                    - You can use a musical instrument (see Equipment) as a spellcasting focus for your\n                      bard spells.\n      '404':\n        description: Not found.\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/error-response'\n            example:\n              error: Not found\n\n# /api/2014/classes/{index}/features\nclass-features-path:\n  get:\n    summary: Get features available for a class.\n    tags:\n      - Class Resource Lists\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/class-index'\n    responses:\n      '200':\n        description: List of features for the class.\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/APIReferenceList'\n# /api/2014/classes/{index}/proficiencies\nclass-proficiencies-path:\n  get:\n    summary: Get proficiencies available for a class.\n    tags:\n      - Class Resource Lists\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/class-index'\n    responses:\n      '200':\n        description: List of proficiencies for the class.\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/APIReferenceList'\n# /api/2014/classes/{index}/multi-classing:\nclass-multi-classing-path:\n  get:\n    summary: Get multiclassing resource for a class.\n    tags:\n      - Class\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/class-index'\n    responses:\n      '200':\n        description: OK\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/Multiclassing'\n            example:\n              prerequisites:\n                - ability_score:\n                    index: str\n                    name: STR\n                    url: '/api/2014/ability-scores/str'\n                  minimum_score: 13\n              proficiencies:\n                - index: shields\n                  name: Shields\n                  url: '/api/2014/proficiencies/shields'\n                - index: simple-weapons\n                  name: Simple Weapons\n                  url: '/api/2014/proficiencies/simple-weapons'\n                - index: martial-weapons\n                  name: Martial Weapons\n                  url: '/api/2014/proficiencies/martial-weapons'\n              proficiency_choices: []\n\n# /api/2014/classes/{index}/levels\nclass-levels-path:\n  get:\n    summary: Get all level resources for a class.\n    tags:\n      - Class Levels\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/class-index'\n      - $ref: '../../parameters/2014/combined.yml#/levels-subclass-filter'\n    responses:\n      '200':\n        description: OK\n        content:\n          application/json:\n            schema:\n              type: array\n              items:\n                $ref: '../../schemas/2014/combined.yml#/ClassLevel'\n# /api/2014/classes/{index}/levels/{class_level}\nclass-level-path:\n  get:\n    summary: Get level resource for a class and level.\n    tags:\n      - Class Levels\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/class-index'\n      - $ref: '../../parameters/2014/combined.yml#/class-level'\n    responses:\n      '200':\n        description: OK\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/ClassLevel'\n            example:\n              level: 1\n              ability_score_bonuses: 0\n              prof_bonus: 2\n              features:\n                - index: rage\n                  name: Rage\n                  url: '/api/2014/features/rage'\n                - index: barbarian-unarmored-defense\n                  name: Unarmored Defense\n                  url: '/api/2014/features/barbarian-unarmored-defense'\n              class_specific:\n                rage_count: 2\n                rage_damage_bonus: 2\n                brutal_critical_dice: 0\n              index: barbarian-1\n              class:\n                index: barbarian\n                name: Barbarian\n                url: '/api/2014/classes/barbarian'\n              url: '/api/2014/classes/barbarian/levels/1'\n# /api/2014/classes/{index}/levels/{class_level}/features:\nclass-level-features-path:\n  get:\n    summary: Get features available to a class at the requested level.\n    tags:\n      - Class Levels\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/class-index'\n      - $ref: '../../parameters/2014/combined.yml#/class-level'\n    responses:\n      '200':\n        description: OK\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/APIReferenceList'\n            example:\n              count: 2\n              results:\n                - index: barbarian-unarmored-defense\n                  name: Unarmored Defense\n                  url: '/api/2014/features/barbarian-unarmored-defense'\n                - index: rage\n                  name: Rage\n                  url: '/api/2014/features/rage'\n# /api/2014/classes/{index}/levels/{spell_level}/spells\nclass-spell-level-spells-path:\n  get:\n    summary: Get spells of the requested level available to the class.\n    tags:\n      - Class Levels\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/class-index'\n      - $ref: '../../parameters/2014/combined.yml#/spell-level'\n    responses:\n      '200':\n        description: OK\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/APIReferenceList'\n            example:\n              count: 5\n              results:\n                - index: dominate-monster\n                  name: Dominate Monster\n                  url: '/api/2014/spells/dominate-monster'\n                - index: earthquake\n                  name: Earthquake\n                  url: '/api/2014/spells/earthquake'\n                - index: incendiary-cloud\n                  name: Incendiary Cloud\n                  url: '/api/2014/spells/incendiary-cloud'\n                - index: power-word-stun\n                  name: Power Word Stun\n                  url: '/api/2014/spells/power-word-stun'\n                - index: sunburst\n                  name: Sunburst\n                  url: '/api/2014/spells/sunburst'\n"
  },
  {
    "path": "src/swagger/paths/2014/combined.yml",
    "content": "base:\n  $ref: './common.yml#/api-base'\nlist:\n  $ref: './common.yml#/resource-list'\nability-scores:\n  $ref: './ability-scores.yml'\nalignments:\n  $ref: './alignments.yml'\nbackgrounds:\n  $ref: './backgrounds.yml'\nclasses:\n  $ref: './classes.yml#/class-path'\nclass-subclass:\n  $ref: './classes.yml#/class-subclass-path'\nclass-spells:\n  $ref: './classes.yml#/class-spells-path'\nclass-spellcasting:\n  $ref: './classes.yml#/class-spellcasting-path'\nclass-features:\n  $ref: './classes.yml#/class-features-path'\nclass-proficiencies:\n  $ref: './classes.yml#/class-proficiencies-path'\nclass-multi-classing:\n  $ref: './classes.yml#/class-multi-classing-path'\nclass-levels:\n  $ref: './classes.yml#/class-levels-path'\nclass-level:\n  $ref: './classes.yml#/class-level-path'\nclass-level-features:\n  $ref: './classes.yml#/class-level-features-path'\nclass-spell-level-spells:\n  $ref: './classes.yml#/class-spell-level-spells-path'\nconditions:\n  $ref: './conditions.yml'\ndamage-types:\n  $ref: './damage-types.yml'\nequipment:\n  $ref: './equipment.yml'\nequipment-categories:\n  $ref: './equipment-categories.yml'\nfeats:\n  $ref: './feats.yml'\nfeatures:\n  $ref: './features.yml'\nlanguages:\n  $ref: './languages.yml'\nmagic-items:\n  $ref: './magic-items.yml'\nmagic-schools:\n  $ref: './magic-schools.yml'\nmonsters:\n  $ref: './monsters.yml#/monster-resource-list'\nmonster:\n  $ref: './monsters.yml#/monster-index'\nproficiencies:\n  $ref: './proficiencies.yml'\nraces:\n  $ref: './races.yml#/race-path'\nrace-subraces:\n  $ref: './races.yml#/race-subraces-path'\nrace-proficiencies:\n  $ref: './races.yml#/race-proficiencies-path'\nrace-traits:\n  $ref: './races.yml#/race-traits-path'\nrule-sections:\n  $ref: './rule-sections.yml'\nrules:\n  $ref: './rules.yml'\nskills:\n  $ref: './skills.yml'\nspells:\n  $ref: './spells.yml#/spells-resource-list'\nspell:\n  $ref: './spells.yml#/spell-by-index'\nsubclasses:\n  $ref: './subclasses.yml#/subclass-path'\nsubclass-features:\n  $ref: './subclasses.yml#/subclass-features-path'\nsubclass-levels:\n  $ref: './subclasses.yml#/subclass-levels-path'\nsubclass-level:\n  $ref: './subclasses.yml#/subclass-level-path'\nsubclass-level-features:\n  $ref: './subclasses.yml#/subclass-level-features-path'\nsubraces:\n  $ref: './subraces.yml#/subraces-path'\nsubrace-proficiencies:\n  $ref: './subraces.yml#/subrace-proficiencies-path'\nsubrace-traits:\n  $ref: './subraces.yml#/subrace-traits-path'\ntraits:\n  $ref: './traits.yml'\nweapon-properties:\n  $ref: './weapon-properties.yml'\n"
  },
  {
    "path": "src/swagger/paths/2014/common.yml",
    "content": "api-base:\n  get:\n    summary: Get all resource URLs.\n    description: Making a request to the API's base URL returns an object containing available endpoints.\n    tags:\n      - Common\n    responses:\n      '200':\n        description: OK\n        content:\n          application/json:\n            schema:\n              type: object\n              additionalProperties:\n                type: string\n            example:\n              ability-scores: '/api/2014/ability-scores'\n              alignments: '/api/2014/alignments'\n              backgrounds: '/api/2014/backgrounds'\n              classes: '/api/2014/classes'\n              conditions: '/api/2014/conditions'\n              damage-types: '/api/2014/damage-types'\n              equipment-categories: '/api/2014/equipment-categories'\n              equipment: '/api/2014/equipment'\n              feats: '/api/2014/feats'\n              features: '/api/2014/features'\n              languages: '/api/2014/languages'\n              magic-items: '/api/2014/magic-items'\n              magic-schools: '/api/2014/magic-schools'\n              monsters: '/api/2014/monsters'\n              proficiencies: '/api/2014/proficiencies'\n              races: '/api/2014/races'\n              rules: '/api/2014/rules'\n              rule-sections: '/api/2014/rule-sections'\n              skills: '/api/2014/skills'\n              spells: '/api/2014/spells'\n              subclasses: '/api/2014/subclasses'\n              subraces: '/api/2014/subraces'\n              traits: '/api/2014/traits'\n              weapon-properties: '/api/2014/weapon-properties'\nresource-list:\n  get:\n    summary: 'Get list of all available resources for an endpoint.'\n    description: |\n      Currently only the [`/spells`](#get-/api/2014/spells) and [`/monsters`](#get-/api/2014/monsters) endpoints support filtering with query parameters. Use of these query parameters is documented under the respective [Spells](#tag--Spells) and [Monsters](#tag--Monsters) sections.\n    tags:\n      - Common\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/base-endpoint-index'\n    responses:\n      '200':\n        description: 'OK'\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/APIReferenceList'\n"
  },
  {
    "path": "src/swagger/paths/2014/conditions.yml",
    "content": "get:\n  summary: 'Get a condition by index.'\n  description: |\n    # Condition\n\n    A condition alters a creature’s capabilities in a variety of ways and can\n    arise as a result of a spell, a class feature, a monster’s attack, or other\n    effect. Most conditions, such as blinded, are impairments, but a few, such\n    as invisible, can be advantageous.\n  tags:\n    - Game Mechanics\n  parameters:\n    - $ref: '../../parameters/2014/combined.yml#/condition-index'\n  responses:\n    '200':\n      description: OK\n      content:\n        application/json:\n          schema:\n            $ref: '../../schemas/2014/combined.yml#/Condition'\n          example:\n            index: blinded\n            name: Blinded\n            url: '/api/2014/conditions/blinded'\n            desc:\n              - \"- A blinded creature can't see and automatically fails any ability check that requires\n                sight.\"\n              - \"- Attack rolls against the creature have advantage, and the creature's attack rolls\n                have disadvantage.\"\n"
  },
  {
    "path": "src/swagger/paths/2014/damage-types.yml",
    "content": "get:\n  summary: 'Get a damage type by index.'\n  description: |\n    # Damage type\n\n    Different attacks, damaging spells, and other harmful effects deal different\n    types of damage. Damage types have no rules of their own, but other rules,\n    such as damage resistance, rely on the types.\n  tags:\n    - Game Mechanics\n  parameters:\n    - $ref: '../../parameters/2014/combined.yml#/damage-type-index'\n  responses:\n    '200':\n      description: 'OK'\n      content:\n        application/json:\n          schema:\n            $ref: '../../schemas/2014/combined.yml#/DamageType'\n          example:\n            index: acid\n            name: Acid\n            url: '/api/2014/damage-types/acid'\n            desc:\n              - \"The corrosive spray of a black dragon's breath and the dissolving enzymes secreted by a black pudding deal acid damage.\"\n"
  },
  {
    "path": "src/swagger/paths/2014/equipment-categories.yml",
    "content": "get:\n  summary: Get an equipment category by index.\n  description: These are the categories that various equipment fall under.\n  tags:\n    - Equipment\n  parameters:\n    - name: index\n      in: path\n      required: true\n      description: |\n        The `index` of the equipment category score to get.\n\n        Available values can be found in the resource list for this endpoint.\n      schema:\n        type: string\n      example: 'waterborne-vehicles'\n  responses:\n    '200':\n      description: OK\n      content:\n        application/json:\n          schema:\n            $ref: '../../schemas/2014/combined.yml#/EquipmentCategory'\n          example:\n            index: waterborne-vehicles\n            name: Waterborne Vehicles\n            url: '/api/2014/equipment-categories/waterborne-vehicles'\n            equipment:\n              - index: galley\n                name: Galley\n                url: '/api/2014/equipment/galley'\n              - index: keelboat\n                name: Keelboat\n                url: '/api/2014/equipment/keelboat'\n              - index: longship\n                name: Longship\n                url: '/api/2014/equipment/longship'\n              - index: rowboat\n                name: Rowboat\n                url: '/api/2014/equipment/rowboat'\n              - index: sailing-ship\n                name: Sailing ship\n                url: '/api/2014/equipment/sailing-ship'\n              - index: warship\n                name: Warship\n                url: '/api/2014/equipment/warship'\n"
  },
  {
    "path": "src/swagger/paths/2014/equipment.yml",
    "content": "get:\n  summary: 'Get an equipment item by index.'\n  description: |\n    # Equipment\n\n    Opportunities abound to find treasure, equipment, weapons, armor, and more\n    in the dungeons you explore. Normally, you can sell your treasures and\n    trinkets when you return to a town or other settlement, provided that you\n    can find buyers and merchants interested in your loot.\n  tags:\n    - Equipment\n  parameters:\n    - $ref: '../../parameters/2014/combined.yml#/equipment-index'\n  responses:\n    '200':\n      description: OK\n      content:\n        application/json:\n          schema:\n            $ref: '../../schemas/2014/combined.yml#/Equipment'\n          example:\n            category_range: Simple Melee\n            contents: []\n            cost:\n              quantity: 1\n              unit: sp\n            damage:\n              damage_dice: 1d4\n              damage_type:\n                index: bludgeoning\n                name: Bludgeoning\n                url: '/api/2014/damage-types/bludgeoning'\n            desc: []\n            equipment_category:\n              index: weapon\n              name: Weapon\n              url: '/api/2014/equipment-categories/weapon'\n            index: club\n            name: Club\n            properties:\n              - index: light\n                name: Light\n                url: '/api/2014/weapon-properties/light'\n              - index: monk\n                name: Monk\n                url: '/api/2014/weapon-properties/monk'\n            range:\n              long:\n              normal: 5\n            special: []\n            url: '/api/2014/equipment/club'\n            weapon_category: Simple\n            weapon_range: Melee\n            weight: 2\n"
  },
  {
    "path": "src/swagger/paths/2014/feats.yml",
    "content": "get:\n  summary: 'Get a feat by index.'\n  description: |\n    # Feat\n\n    A feat is a boon a character can receive at level up instead of an ability score increase.\n  tags:\n    - Feats\n  parameters:\n    - name: index\n      in: path\n      required: true\n      description: |\n        The `index` of the feat to get.\n      schema:\n        type: string\n        enum: [grappler]\n  responses:\n    '200':\n      description: 'OK'\n      content:\n        application/json:\n          schema:\n            $ref: '../../schemas/2014/combined.yml#/Feat'\n          example:\n            index: grappler\n            name: Grappler\n            url: '/api/2014/feats/grappler'\n            desc:\n              - 'You’ve developed the Skills necessary to hold your own in close--quarters Grappling.\n                You gain the following benefits:'\n              - '- You have advantage on Attack Rolls against a creature you are Grappling.'\n              - '- You can use your action to try to pin a creature Grappled by you. To do so, make\n                another grapple check. If you succeed, you and the creature are both Restrained\n                until the grapple ends.'\n            prerequisites:\n              - ability_score:\n                  index: str\n                  name: STR\n                  url: '/api/2014/ability-scores/str'\n                minimum_score: 13\n"
  },
  {
    "path": "src/swagger/paths/2014/features.yml",
    "content": "get:\n  summary: Get a feature by index.\n  description: |\n    # Feature\n\n    When you gain a new level in a class, you get its features for that level.\n    You don’t, however, receive the class’s starting Equipment, and a few\n    features have additional rules when you’re multiclassing: Channel Divinity,\n    Extra Attack, Unarmored Defense, and Spellcasting.\n  tags:\n    - Features\n  parameters:\n    - $ref: '../../parameters/2014/combined.yml#/feature-index'\n  responses:\n    '200':\n      description: OK\n      content:\n        application/json:\n          schema:\n            $ref: '../../schemas/2014/combined.yml#/Feature'\n          example:\n            index: action-surge-1-use\n            name: 'Action Surge (1 use)'\n            url: '/api/2014/features/action-surge-1-use'\n            class:\n              index: fighter\n              name: Fighter\n              url: '/api/2014/classes/fighter'\n            desc:\n              - Starting at 2nd level, you can push yourself beyond your normal limits for a moment.\n                On your turn, you can take one additional action on top of your regular action and\n                a possible bonus action.\n              - Once you use this feature, you must finish a short or long rest before you can use\n                it again. Starting at 17th level, you can use it twice before a rest, but only once\n                on the same turn.\n            level: 2\n            prerequisites: []\n"
  },
  {
    "path": "src/swagger/paths/2014/languages.yml",
    "content": "get:\n  summary: Get a language by index.\n  description: |\n    # Language\n\n    Your race indicates the languages your character can speak by default, and your background might give you access to one or more additional languages of your choice. [[SRD p59](https://media.wizards.com/2016/downloads/DND/SRD-OGL_V5.1.pdf#page=59)]\n  tags:\n    - Character Data\n  parameters:\n    - $ref: '../../parameters/2014/combined.yml#/language-index'\n  responses:\n    '200':\n      description: 'OK'\n      content:\n        application/json:\n          schema:\n            $ref: '../../schemas/2014/combined.yml#/Language'\n          example:\n            index: elvish\n            name: Elvish\n            url: '/api/2014/languages/elvish'\n            desc: 'Elvish is fluid, with subtle intonations and intricate grammar. Elven literature is rich and varied, and their songs and poems are famous among other races. Many bards learn their language so they can add Elvish ballads to their repertoires.'\n            type: Standard\n            script: Elvish\n            typical_speakers:\n              - Elves\n"
  },
  {
    "path": "src/swagger/paths/2014/magic-items.yml",
    "content": "get:\n  summary: Get a magic item by index.\n  description: These are the various magic items you can find in the game.\n  tags:\n    - Equipment\n  parameters:\n    - name: index\n      in: path\n      required: true\n      description: |\n        The `index` of the magic item to get.\n\n        Available values can be found in the resource list for this endpoint.\n      schema:\n        type: string\n      example: 'adamantine-armor'\n  responses:\n    '200':\n      description: OK\n      content:\n        application/json:\n          schema:\n            $ref: '../../schemas/2014/combined.yml#/MagicItem'\n          example:\n            index: adamantine-armor\n            name: Adamantine Armor\n            url: '/api/2014/magic-items/adamantine-armor'\n            desc:\n              - Armor (medium or heavy, but not hide), uncommon\n              - This suit of armor is reinforced with adamantine, one of the hardest substances\n                in existence. While you're wearing it, any critical hit against you becomes a normal\n                hit.\n            equipment_category:\n              index: armor\n              name: Armor\n              url: '/api/2014/equipment-categories/armor'\n            rarity:\n              name: Uncommon\n            variants: []\n            variant: false\n"
  },
  {
    "path": "src/swagger/paths/2014/magic-schools.yml",
    "content": "get:\n  summary: Get a magic school by index.\n  description: |\n    # Magic School\n\n    Academies of magic group spells into eight categories called schools of\n    magic. Scholars, particularly wizards, apply these categories to all spells,\n    believing that all magic functions in essentially the same way, whether it\n    derives from rigorous study or is bestowed by a deity.\n  tags:\n    - Game Mechanics\n  parameters:\n    - $ref: '../../parameters/2014/combined.yml#/magic-school-index'\n  responses:\n    '200':\n      description: OK\n      content:\n        application/json:\n          schema:\n            $ref: '../../schemas/2014/combined.yml#/MagicSchool'\n          example:\n            index: conjuration\n            name: Conjuration\n            url: '/api/2014/magic-schools/conjuration'\n            desc: \"Conjuration spells involve the transportation of objects and creatures from one location to another. Some spells summon creatures or objects to the caster's side, whereas others allow the caster to teleport to another location. Some conjurations create objects or effects out of nothing.\"\n"
  },
  {
    "path": "src/swagger/paths/2014/monsters.yml",
    "content": "monster-resource-list:\n  get:\n    summary: 'Get list of monsters with optional filtering'\n    tags:\n      - Monsters\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/challenge-rating-filter'\n    responses:\n      '200':\n        description: OK\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/APIReferenceList'\nmonster-index:\n  get:\n    summary: Get monster by index.\n    tags:\n      - Monsters\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/monster-index'\n    responses:\n      '200':\n        description: OK\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/Monster'\n            example:\n              index: aboleth\n              name: Aboleth\n              url: '/api/2014/monsters/aboleth'\n              actions:\n                - attacks: []\n                  damage: []\n                  desc: The aboleth makes three tentacle attacks.\n                  name: Multiattack\n                  multiattack_type: actions\n                  actions:\n                    - action_name: Tentacle\n                      count: 3\n                      type: melee\n                - attack_bonus: 9\n                  attacks: []\n                  damage:\n                    - damage_dice: 2d6+5\n                      damage_type:\n                        index: bludgeoning\n                        name: Bludgeoning\n                        url: '/api/2014/damage-types/bludgeoning'\n                    - damage_dice: 1d12\n                      damage_type:\n                        index: acid\n                        name: Acid\n                        url: '/api/2014/damage-types/acid'\n                  dc:\n                    dc_type:\n                      index: con\n                      name: CON\n                      url: '/api/2014/ability-scores/con'\n                    dc_value: 14\n                    success_type: none\n                  desc: \"Melee Weapon Attack: +9 to hit, reach 10 ft., one target. Hit: 12 (2d6 +\n                    5) bludgeoning damage. If the target is a creature, it must succeed on a DC 14\n                    Constitution saving throw or become diseased. The disease has no effect for 1\n                    minute and can be removed by any magic that cures disease. After 1 minute, the\n                    diseased creature's skin becomes translucent and slimy, the creature can't regain\n                    hit points unless it is underwater, and the disease can be removed only by heal\n                    or another disease-curing spell of 6th level or higher. When the creature is outside\n                    a body of water, it takes 6 (1d12) acid damage every 10 minutes unless moisture\n                    is applied to the skin before 10 minutes have passed.\"\n                  name: Tentacle\n                - attack_bonus: 9\n                  attacks: []\n                  damage:\n                    - damage_dice: 3d6+5\n                      damage_type:\n                        index: bludgeoning\n                        name: Bludgeoning\n                        url: '/api/2014/damage-types/bludgeoning'\n                  desc: 'Melee Weapon Attack: +9 to hit, reach 10 ft. one target. Hit: 15 (3d6 + 5)\n                    bludgeoning damage.'\n                  name: Tail\n                - attacks: []\n                  damage: []\n                  dc:\n                    dc_type:\n                      index: wis\n                      name: WIS\n                      url: '/api/2014/ability-scores/wis'\n                    dc_value: 14\n                    success_type: none\n                  desc: |-\n                    The aboleth targets one creature it can see within 30 ft. of it. The target must succeed on a DC 14 Wisdom saving throw or be magically charmed by the aboleth until the aboleth dies or until it is on a different plane of existence from the target. The charmed target is under the aboleth's control and can't take reactions, and the aboleth and the target can communicate telepathically with each other over any distance.\n                    Whenever the charmed target takes damage, the target can repeat the saving throw. On a success, the effect ends. No more than once every 24 hours, the target can also repeat the saving throw when it is at least 1 mile away from the aboleth.\n                  name: Enslave\n                  usage:\n                    times: 3\n                    type: per day\n              alignment: lawful evil\n              armor_class:\n                - type: natural\n                  value: 17\n              challenge_rating: 10\n              proficiency_bonus: 4\n              charisma: 18\n              condition_immunities: []\n              constitution: 15\n              damage_immunities: []\n              damage_resistances: []\n              damage_vulnerabilities: []\n              dexterity: 9\n              forms: []\n              hit_dice: 18d10\n              hit_points: 135\n              hit_points_roll: 18d10+36\n              intelligence: 18\n              languages: Deep Speech, telepathy 120 ft.\n              legendary_actions:\n                - damage: []\n                  desc: The aboleth makes a Wisdom (Perception) check.\n                  name: Detect\n                - damage: []\n                  desc: The aboleth makes one tail attack.\n                  name: Tail Swipe\n                - attack_bonus: 0\n                  damage:\n                    - damage_dice: 3d6\n                      damage_type:\n                        index: psychic\n                        name: Psychic\n                        url: '/api/2014/damage-types/psychic'\n                  desc: One creature charmed by the aboleth takes 10 (3d6) psychic damage, and the\n                    aboleth regains hit points equal to the damage the creature takes.\n                  name: Psychic Drain (Costs 2 Actions)\n              proficiencies:\n                - proficiency:\n                    index: saving-throw-con\n                    name: 'Saving Throw: CON'\n                    url: '/api/2014/proficiencies/saving-throw-con'\n                  value: 6\n                - proficiency:\n                    index: saving-throw-int\n                    name: 'Saving Throw: INT'\n                    url: '/api/2014/proficiencies/saving-throw-int'\n                  value: 8\n                - proficiency:\n                    index: saving-throw-wis\n                    name: 'Saving Throw: WIS'\n                    url: '/api/2014/proficiencies/saving-throw-wis'\n                  value: 6\n                - proficiency:\n                    index: skill-history\n                    name: 'Skill: History'\n                    url: '/api/2014/proficiencies/skill-history'\n                  value: 12\n                - proficiency:\n                    index: skill-perception\n                    name: 'Skill: Perception'\n                    url: '/api/2014/proficiencies/skill-perception'\n                  value: 10\n              reactions: []\n              senses:\n                darkvision: 120 ft.\n                passive_perception: 20\n              size: Large\n              special_abilities:\n                - damage: []\n                  desc: The aboleth can breathe air and water.\n                  name: Amphibious\n                - damage: []\n                  dc:\n                    dc_type:\n                      index: con\n                      name: CON\n                      url: '/api/2014/ability-scores/con'\n                    dc_value: 14\n                    success_type: none\n                  desc:\n                    While underwater, the aboleth is surrounded by transformative mucus. A creature\n                    that touches the aboleth or that hits it with a melee attack while within 5 ft.\n                    of it must make a DC 14 Constitution saving throw. On a failure, the creature\n                    is diseased for 1d4 hours. The diseased creature can breathe only underwater.\n                  name: Mucous Cloud\n                - damage: []\n                  desc:\n                    If a creature communicates telepathically with the aboleth, the aboleth learns\n                    the creature's greatest desires if the aboleth can see the creature.\n                  name: Probing Telepathy\n              speed:\n                swim: 40 ft.\n                walk: 10 ft.\n              strength: 21\n              subtype:\n              type: aberration\n              wisdom: 15\n              xp: 5900\n"
  },
  {
    "path": "src/swagger/paths/2014/proficiencies.yml",
    "content": "get:\n  summary: 'Get a proficiency by index.'\n  description: |\n    # Proficiency\n\n    By virtue of race, class, and background a character is proficient at using certain skills, weapons, and equipment. Characters can also gain additional proficiencies at higher levels or by multiclassing. A characters starting proficiencies are determined during character creation.\n  tags:\n    - Character Data\n  parameters:\n    - $ref: '../../parameters/2014/combined.yml#/proficiency-index'\n  responses:\n    '200':\n      description: 'OK'\n      content:\n        application/json:\n          schema:\n            $ref: '../../schemas/2014/combined.yml#/Proficiency'\n          example:\n            index: medium-armor\n            name: Medium Armor\n            url: '/api/2014/proficiencies/medium-armor'\n            type: Armor\n            classes:\n              - index: barbarian\n                name: Barbarian\n                url: '/api/2014/classes/barbarian'\n              - index: cleric\n                name: Cleric\n                url: '/api/2014/classes/cleric'\n              - index: druid\n                name: Druid\n                url: '/api/2014/classes/druid'\n              - index: ranger\n                name: Ranger\n                url: '/api/2014/classes/ranger'\n            races: []\n            reference:\n              index: medium-armor\n              name: Medium Armor\n              url: '/api/2014/equipment-categories/medium-armor'\n"
  },
  {
    "path": "src/swagger/paths/2014/races.yml",
    "content": "# /api/2014/races/{index}\nrace-path:\n  get:\n    summary: Get a race by index.\n    description: Each race grants your character ability and skill bonuses as well as racial traits.\n    tags:\n      - Races\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/race-index'\n    responses:\n      '200':\n        description: OK\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/Race'\n            example:\n              index: elf\n              name: Elf\n              url: '/api/2014/races/elf'\n              ability_bonuses:\n                - ability_score:\n                    index: dex\n                    name: DEX\n                    url: '/api/2014/ability-scores/dex'\n                  bonus: 2\n              age: Although elves reach physical maturity at about the same age as humans, the elven\n                understanding of adulthood goes beyond physical growth to encompass worldly experience.\n                An elf typically claims adulthood and an adult name around the age of 100 and can\n                live to be 750 years old.\n              alignment: Elves love freedom, variety, and self-expression, so they lean strongly\n                toward the gentler aspects of chaos. They value and protect others' freedom as well\n                as their own, and they are more often good than not. The drow are an exception;\n                their exile has made them vicious and dangerous. Drow are more often evil than not.\n              language_desc: You can speak, read, and write Common and Elvish. Elvish is fluid,\n                with subtle intonations and intricate grammar. Elven literature is rich and varied,\n                and their songs and poems are famous among other races. Many bards learn their language\n                so they can add Elvish ballads to their repertoires.\n              languages:\n                - index: common\n                  name: Common\n                  url: '/api/2014/languages/common'\n                - index: elvish\n                  name: Elvish\n                  url: '/api/2014/languages/elvish'\n              size: Medium\n              size_description:\n                Elves range from under 5 to over 6 feet tall and have slender builds.\n                Your size is Medium.\n              speed: 30\n              subraces:\n                - index: high-elf\n                  name: High Elf\n                  url: '/api/2014/subraces/high-elf'\n              traits:\n                - index: darkvision\n                  name: Darkvision\n                  url: '/api/2014/traits/darkvision'\n                - index: fey-ancestry\n                  name: Fey Ancestry\n                  url: '/api/2014/traits/fey-ancestry'\n                - index: trance\n                  name: Trance\n                  url: '/api/2014/traits/trance'\n# /api/2014/races/{index}/subraces:\nrace-subraces-path:\n  get:\n    summary: 'Get subraces available for a race.'\n    tags:\n      - Races\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/race-index'\n    responses:\n      '200':\n        description: 'List of subraces for the race.'\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/APIReferenceList'\n# /api/2014/races/{index}/proficiencies:\nrace-proficiencies-path:\n  get:\n    summary: 'Get proficiencies available for a race.'\n    tags:\n      - Races\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/race-index'\n    responses:\n      '200':\n        description: 'List of proficiencies for the race.'\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/APIReferenceList'\n# /api/2014/races/{index}/traits:\nrace-traits-path:\n  get:\n    summary: 'Get traits available for a race.'\n    tags:\n      - Races\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/race-index'\n    responses:\n      '200':\n        description: 'List of traits for the race.'\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/APIReferenceList'\n"
  },
  {
    "path": "src/swagger/paths/2014/rule-sections.yml",
    "content": "get:\n  summary: 'Get a rule section by index.'\n  description: Rule sections represent a sub-heading and text that can be found underneath a rule heading in the SRD.\n  tags:\n    - Rules\n  parameters:\n    - $ref: '../../parameters/2014/combined.yml#/rule-section-index'\n  responses:\n    '200':\n      description: OK\n      content:\n        application/json:\n          schema:\n            $ref: '../../schemas/2014/combined.yml#/RuleSection'\n          example:\n            index: time\n            name: Time\n            url: '/api/2014/rule-sections/time'\n            desc: |\n              ## Time\n\n              In situations where keeping track of the passage of time is important, the GM determines the time a task requires. The GM might use a different time scale depending on the context of the situation at hand. In a dungeon environment, the adventurers' movement happens on a scale of **minutes**. It takes them about a minute to creep down a long hallway, another minute to check for traps on the door at the end of the hall, and a good ten minutes to search the chamber beyond for anything interesting or valuable.\n\n              In a city or wilderness, a scale of **hours** is often more appropriate. Adventurers eager to reach the lonely tower at the heart of the forest hurry across those fifteen miles in just under four hours' time.\n\n              For long journeys, a scale of **days** works best.\n\n              Following the road from Baldur's Gate to Waterdeep, the adventurers spend four uneventful days before a goblin ambush interrupts their journey.\n\n              In combat and other fast-paced situations, the game relies on **rounds**, a 6-second span of time.\n"
  },
  {
    "path": "src/swagger/paths/2014/rules.yml",
    "content": "get:\n  summary: 'Get a rule by index.'\n  description: |\n    # Rule\n\n    Rules are pages in the SRD that document the mechanics of Dungeons and Dragons.\n    Rules have descriptions which is the text directly underneath the rule heading\n    in the SRD. Rules also have subsections for each heading underneath the rule in the SRD.\n  tags:\n    - Rules\n  parameters:\n    - $ref: '../../parameters/2014/combined.yml#/rule-index'\n  responses:\n    '200':\n      description: OK\n      content:\n        application/json:\n          schema:\n            $ref: '../../schemas/2014/combined.yml#/Rule'\n          example:\n            index: using-ability-scores\n            name: Using Ability Scores\n            url: '/api/2014/rules/using-ability-scores'\n            desc: |\n              # Using Ability Scores\n\n              Six abilities provide a quick description of every creature's physical and mental characteristics:\n              - **Strength**, measuring physical power\n              - **Dexterity**, measuring agility\n              - **Constitution**, measuring endurance\n              - **Intelligence**, measuring reasoning and memory\n              - **Wisdom**, measuring perception and insight\n              - **Charisma**, measuring force of personality\n\n              Is a character muscle-bound and insightful? Brilliant and charming? Nimble and hardy? Ability scores define these qualities-a creature's assets as well as weaknesses.\n\n              The three main rolls of the game-the ability check, the saving throw, and the attack roll-rely on the six ability scores. The book's introduction describes the basic rule behind these rolls: roll a d20, add an ability modifier derived from one of the six ability scores, and compare the total to a target number.\n\n              **Ability Scores and Modifiers** Each of a creature's abilities has a score, a number that defines the magnitude of that ability. An ability score is not just a measure of innate capabilities, but also encompasses a creature's training and competence in activities related to that ability.\n\n              A score of 10 or 11 is the normal human average, but adventurers and many monsters are a cut above average in most abilities. A score of 18 is the highest that a person usually reaches. Adventurers can have scores as high as 20, and monsters and divine beings can have scores as high as 30.\n\n              Each ability also has a modifier, derived from the score and ranging from -5 (for an ability score of 1) to +10 (for a score of 30). The Ability Scores and Modifiers table notes the ability modifiers for the range of possible ability scores, from 1 to 30.\n            subsections:\n              - index: ability-scores-and-modifiers\n                name: Ability Scores and Modifiers\n                url: '/api/2014/rule-sections/ability-scores-and-modifiers'\n              - index: advantage-and-disadvantage\n                name: Advantage and Disadvantage\n                url: '/api/2014/rule-sections/advantage-and-disadvantage'\n              - index: proficiency-bonus\n                name: Proficiency Bonus\n                url: '/api/2014/rule-sections/proficiency-bonus'\n              - index: ability-checks\n                name: Ability Checks\n                url: '/api/2014/rule-sections/ability-checks'\n              - index: using-each-ability\n                name: Using Each Ability\n                url: '/api/2014/rule-sections/using-each-ability'\n              - index: saving-throws\n                name: Saving Throws\n                url: '/api/2014/rule-sections/saving-throws'\n"
  },
  {
    "path": "src/swagger/paths/2014/skills.yml",
    "content": "get:\n  summary: Get a skill by index.\n  description: |\n    # Skill\n\n    Each ability covers a broad range of capabilities, including skills that a character or a monster can be proficient in. A skill represents a specific aspect of an ability score, and an individual's proficiency in a skill demonstrates a focus on that aspect. [[SRD p77](https://media.wizards.com/2016/downloads/DND/SRD-OGL_V5.1.pdf#page=77)]\n  tags:\n    - Character Data\n  parameters:\n    - $ref: '../../parameters/2014/combined.yml#/skill-index'\n  responses:\n    '200':\n      description: 'OK'\n      content:\n        application/json:\n          schema:\n            $ref: '../../schemas/2014/combined.yml#/Skill'\n          example:\n            index: acrobatics\n            name: Acrobatics\n            url: '/api/2014/skills/acrobatics'\n            ability_score:\n              index: dex\n              name: DEX\n              url: '/api/2014/ability-scores/dex'\n            desc:\n              - Your Dexterity (Acrobatics) check covers your attempt to stay on your feet in a\n                tricky situation, such as when you're trying to run across a sheet of ice, balance\n                on a tightrope, or stay upright on a rocking ship's deck. The GM might also call\n                for a Dexterity (Acrobatics) check to see if you can perform acrobatic stunts, including dives, rolls, somersaults, and flips.\n"
  },
  {
    "path": "src/swagger/paths/2014/spells.yml",
    "content": "spells-resource-list:\n  get:\n    summary: 'Get list of spells with optional filtering.'\n    tags:\n      - Spells\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/level-filter'\n      - $ref: '../../parameters/2014/combined.yml#/school-filter'\n    responses:\n      '200':\n        description: 'OK'\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/APIReferenceList'\n\nspell-by-index:\n  get:\n    summary: Get a spell by index.\n    tags:\n      - Spells\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/spell-index'\n    responses:\n      '200':\n        description: OK\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/Spell'\n            example:\n              index: sacred-flame\n              name: Sacred Flame\n              url: '/api/2014/spells/sacred-flame'\n              attack_type: ranged\n              casting_time: 1 action\n              classes:\n                - index: cleric\n                  name: Cleric\n                  url: '/api/2014/classes/cleric'\n              components:\n                - V\n                - S\n              concentration: false\n              damage:\n                damage_at_character_level:\n                  '1': 1d8\n                  '5': 2d8\n                  '11': 3d8\n                  '17': 4d8\n                damage_type:\n                  index: radiant\n                  name: Radiant\n                  url: '/api/2014/damage-types/radiant'\n              dc:\n                dc_success: none\n                dc_type:\n                  index: dex\n                  name: DEX\n                  url: '/api/2014/ability-scores/dex'\n              desc:\n                - Flame-like radiance descends on a creature that you can see within range. The target\n                  must succeed on a dexterity saving throw or take 1d8 radiant damage. The target\n                  gains no benefit from cover for this saving throw.\n                - The spell's damage increases by 1d8 when you reach 5th level (2d8), 11th level (3d8),\n                  and 17th level (4d8).\n              duration: Instantaneous\n              higher_level: []\n              level: 0\n              range: 60 feet\n              ritual: false\n              school:\n                index: evocation\n                name: Evocation\n                url: '/api/2014/magic-schools/evocation'\n              subclasses:\n                - index: lore\n                  name: Lore\n                  url: '/api/2014/subclasses/lore'\n"
  },
  {
    "path": "src/swagger/paths/2014/subclasses.yml",
    "content": "# /api/2014/subclass/{index}\nsubclass-path:\n  get:\n    summary: Get a subclass by index.\n    description: Subclasses reflect the different paths a class may take as levels are gained.\n    tags:\n      - Subclasses\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/subclass-index'\n    responses:\n      '200':\n        description: OK\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/Subclass'\n            example:\n              index: fiend\n              name: Fiend\n              url: '/api/2014/subclasses/fiend'\n              class:\n                index: warlock\n                name: Warlock\n                url: '/api/2014/classes/warlock'\n              desc:\n                - You have made a pact with a fiend from the lower planes of existence, a being whose\n                  aims are evil, even if you strive against those aims. Such beings desire the corruption\n                  or destruction of all things, ultimately including you. Fiends powerful enough to\n                  forge a pact include demon lords such as Demogorgon, Orcus, Fraz'Urb-luu, and Baphomet;\n                  archdevils such as Asmodeus, Dispater, Mephistopheles, and Belial; pit fiends and\n                  balors that are especially mighty; and ultroloths and other lords of the yugoloths.\n              spells:\n                - prerequisites:\n                    - index: warlock-1\n                      name: Warlock 1\n                      type: level\n                      url: '/api/2014/classes/warlock/levels/1'\n                  spell:\n                    index: burning-hands\n                    name: Burning Hands\n                    url: '/api/2014/spells/burning-hands'\n                - prerequisites:\n                    - index: warlock-1\n                      name: Warlock 1\n                      type: level\n                      url: '/api/2014/classes/warlock/levels/1'\n                  spell:\n                    index: command\n                    name: Command\n                    url: '/api/2014/spells/command'\n                - prerequisites:\n                    - index: warlock-3\n                      name: Warlock 3\n                      type: level\n                      url: '/api/2014/classes/warlock/levels/3'\n                  spell:\n                    index: blindness-deafness\n                    name: Blindness/Deafness\n                    url: '/api/2014/spells/blindness-deafness'\n                - prerequisites:\n                    - index: warlock-3\n                      name: Warlock 3\n                      type: level\n                      url: '/api/2014/classes/warlock/levels/3'\n                  spell:\n                    index: scorching-ray\n                    name: Scorching Ray\n                    url: '/api/2014/spells/scorching-ray'\n                - prerequisites:\n                    - index: warlock-5\n                      name: Warlock 5\n                      type: level\n                      url: '/api/2014/classes/warlock/levels/5'\n                  spell:\n                    index: fireball\n                    name: Fireball\n                    url: '/api/2014/spells/fireball'\n                - prerequisites:\n                    - index: warlock-5\n                      name: Warlock 5\n                      type: level\n                      url: '/api/2014/classes/warlock/levels/5'\n                  spell:\n                    index: stinking-cloud\n                    name: Stinking Cloud\n                    url: '/api/2014/spells/stinking-cloud'\n                - prerequisites:\n                    - index: warlock-7\n                      name: Warlock 7\n                      type: level\n                      url: '/api/2014/classes/warlock/levels/7'\n                  spell:\n                    index: fire-shield\n                    name: Fire Shield\n                    url: '/api/2014/spells/fire-shield'\n                - prerequisites:\n                    - index: warlock-7\n                      name: Warlock 7\n                      type: level\n                      url: '/api/2014/classes/warlock/levels/7'\n                  spell:\n                    index: wall-of-fire\n                    name: Wall of Fire\n                    url: '/api/2014/spells/wall-of-fire'\n                - prerequisites:\n                    - index: warlock-9\n                      name: Warlock 9\n                      type: level\n                      url: '/api/2014/classes/warlock/levels/9'\n                  spell:\n                    index: flame-strike\n                    name: Flame Strike\n                    url: '/api/2014/spells/flame-strike'\n                - prerequisites:\n                    - index: warlock-9\n                      name: Warlock 9\n                      type: level\n                      url: '/api/2014/classes/warlock/levels/9'\n                  spell:\n                    index: hallow\n                    name: Hallow\n                    url: '/api/2014/spells/hallow'\n              subclass_flavor: Otherworldly Patron\n              subclass_levels: '/api/2014/subclasses/fiend/levels'\n# /api/2014/subclasses/{index}/features:\nsubclass-features-path:\n  get:\n    summary: 'Get features available for a subclass.'\n    tags:\n      - Subclasses\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/subclass-index'\n    responses:\n      '200':\n        description: 'List of features for the subclass.'\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/APIReferenceList'\n# /api/2014/subclasses/{index}/levels:\nsubclass-levels-path:\n  get:\n    summary: 'Get all level resources for a subclass.'\n    tags:\n      - Subclasses\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/subclass-index'\n    responses:\n      '200':\n        description: 'List of level resource for the subclass.'\n        content:\n          application/json:\n            schema:\n              type: array\n              items:\n                $ref: '../../schemas/2014/combined.yml#/SubclassLevelResource'\n# /api/2014/subclasses/{index}/levels/{subclass_level}:\nsubclass-level-path:\n  get:\n    summary: 'Get level resources for a subclass and level.'\n    tags:\n      - Subclasses\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/subclass-index'\n      - name: subclass_level\n        in: path\n        required: true\n        schema:\n          type: integer\n          minimum: 1\n          maximum: 20\n        example: 6\n    responses:\n      '200':\n        description: 'Level resource for the subclass and level.'\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/SubclassLevel'\n            example:\n              index: devotion-3\n              url: '/api/2014/subclasses/devotion/levels/3'\n              class:\n                index: paladin\n                name: Paladin\n                url: '/api/2014/classes/paladin'\n              features:\n                - index: channel-divinity\n                  name: Channel Divinity\n                  url: '/api/2014/features/channel-divinity'\n              level: 3\n              subclass:\n                index: devotion\n                name: Devotion\n                url: '/api/2014/subclasses/devotion'\n# /api/2014/subclasses/{index}/levels/{subclass_level}/features:\nsubclass-level-features-path:\n  get:\n    summary: 'Get features of the requested spell level available to the class.'\n    tags:\n      - Subclasses\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/subclass-index'\n      - name: subclass_level\n        in: path\n        required: true\n        schema:\n          type: integer\n          minimum: 0\n          maximum: 20\n        example: 6\n    responses:\n      '200':\n        description: 'List of features for the subclass and level.'\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/APIReferenceList'\n"
  },
  {
    "path": "src/swagger/paths/2014/subraces.yml",
    "content": "# /api/2014/subraces/{index}\nsubraces-path:\n  get:\n    summary: Get a subrace by index.\n    description: Subraces reflect the different varieties of a certain parent race.\n    tags:\n      - Subraces\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/subrace-index'\n    responses:\n      '200':\n        description: OK\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/Subrace'\n            example:\n              index: hill-dwarf\n              name: Hill Dwarf\n              url: '/api/2014/subraces/hill-dwarf'\n              ability_bonuses:\n                - ability_score:\n                    index: wis\n                    name: WIS\n                    url: '/api/2014/ability-scores/wis'\n                  bonus: 1\n              desc: As a hill dwarf, you have keen senses, deep intuition, and remarkable resilience.\n              race:\n                index: dwarf\n                name: Dwarf\n                url: '/api/2014/races/dwarf'\n              racial_traits:\n                - index: dwarven-toughness\n                  name: Dwarven Toughness\n                  url: '/api/2014/traits/dwarven-toughness'\n\n# /api/2014/subraces/{index}/proficiencies\nsubrace-proficiencies-path:\n  get:\n    summary: 'Get proficiences available for a subrace.'\n    tags:\n      - Subraces\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/subrace-index'\n    responses:\n      '200':\n        description: 'List of proficiences for the subrace.'\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/APIReferenceList'\n# /api/2014/subraces/{index}/traits\nsubrace-traits-path:\n  get:\n    summary: 'Get traits available for a subrace.'\n    tags:\n      - Subraces\n    parameters:\n      - $ref: '../../parameters/2014/combined.yml#/subrace-index'\n    responses:\n      '200':\n        description: 'List of traits for the subrace.'\n        content:\n          application/json:\n            schema:\n              $ref: '../../schemas/2014/combined.yml#/APIReferenceList'\n"
  },
  {
    "path": "src/swagger/paths/2014/traits.yml",
    "content": "get:\n  summary: 'Get a trait by index.'\n  tags:\n    - Traits\n  parameters:\n    - $ref: '../../parameters/2014/combined.yml#/trait-index'\n  responses:\n    '200':\n      description: OK\n      content:\n        application/json:\n          schema:\n            $ref: '../../schemas/2014/combined.yml#/Trait'\n          example:\n            index: trance\n            name: Trance\n            url: '/api/2014/traits/trance'\n            desc:\n              - Elves do not need to sleep. Instead, they meditate deeply, remaining semiconscious,\n                for 4 hours a day. (The Common word for such meditation is \"trance.\") While meditating,\n                you can dream after a fashion; such dreams are actually mental exercises that have\n                become reflexive through years of practice. After resting this way, you gain the\n                same benefit that a human does from 8 hours of sleep.\n            proficiencies: []\n            races:\n              - index: elf\n                name: Elf\n                url: '/api/2014/races/elf'\n            subraces: []\n"
  },
  {
    "path": "src/swagger/paths/2014/weapon-properties.yml",
    "content": "get:\n  summary: 'Get a weapon property by index.'\n  tags:\n    - Equipment\n  parameters:\n    - $ref: '../../parameters/2014/combined.yml#/weapon-property-index'\n  responses:\n    '200':\n      description: OK\n      content:\n        application/json:\n          schema:\n            $ref: '../../schemas/2014/combined.yml#/WeaponProperty'\n          example:\n            index: ammunition\n            name: Ammunition\n            url: '/api/2014/weapon-properties/ammunition'\n            desc:\n              - You can use a weapon that has the ammunition property to make a ranged attack only\n                if you have ammunition to fire from the weapon. Each time you attack with the weapon,\n                you expend one piece of ammunition. Drawing the ammunition from a quiver, case,\n                or other container is part of the attack (you need a free hand to load a one-handed\n                weapon).\n              - At the end of the battle, you can recover half your expended ammunition by taking\n                a minute to search the battlefield. If you use a weapon that has the ammunition\n                property to make a melee attack, you treat the weapon as an improvised weapon (see\n                \"Improvised Weapons\" later in the section). A sling must be loaded to deal any damage\n                when used in this way.\n"
  },
  {
    "path": "src/swagger/paths/2024/.keepme",
    "content": ""
  },
  {
    "path": "src/swagger/schemas/2014/ability-scores.yml",
    "content": "ability-score-model:\n  description: |\n    `AbilityScore`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - $ref: './combined.yml#/ResourceDescription'\n    - type: object\n      properties:\n        full_name:\n          description: 'Full name of the ability score.'\n          type: string\n        skills:\n          description: 'List of skills that use this ability score.'\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\n\nAbilityBonus:\n  type: object\n  properties:\n    bonus:\n      description: 'Bonus amount for this ability score.'\n      type: number\n    ability_score:\n      $ref: './combined.yml#/APIReference'\n"
  },
  {
    "path": "src/swagger/schemas/2014/alignments.yml",
    "content": "description: |\n  `Alignment`\nallOf:\n  - $ref: './combined.yml#/APIReference'\n  - type: object\n    properties:\n      desc:\n        description: Brief description of the resource.\n        type: string\n      abbreviation:\n        description: Abbreviation/initials/acronym for the alignment.\n        type: string\n"
  },
  {
    "path": "src/swagger/schemas/2014/armor.yml",
    "content": "description: |\n  `Armor`\nallOf:\n  - $ref: './combined.yml#/APIReference'\n  - $ref: './combined.yml#/ResourceDescription'\n  - type: object\n    properties:\n      equipment_category:\n        $ref: './combined.yml#/APIReference'\n      armor_category:\n        description: 'The category of armor this falls into.'\n        type: string\n      armor_class:\n        description: 'Details on how to calculate armor class.'\n        type: object\n        additionalProperties:\n          type: string\n      str_minimum:\n        description: 'Minimum STR required to use this armor.'\n        type: number\n      stealth_disadvantage:\n        description: 'Whether the armor gives disadvantage for Stealth.'\n        type: boolean\n      cost:\n        $ref: './combined.yml#/Cost'\n      weight:\n        description: 'How much the equipment weighs.'\n        type: number\n"
  },
  {
    "path": "src/swagger/schemas/2014/backgrounds.yml",
    "content": "background-model:\n  description: |\n    `Background`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - type: object\n      properties:\n        starting_proficiencies:\n          description: 'Starting proficiencies for all new characters of this background.'\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\n        starting_equipment:\n          description: 'Starting equipment for all new characters of this background.'\n          type: array\n          items:\n            type: object\n            properties:\n              quantity:\n                type: number\n              equipment:\n                $ref: './combined.yml#/APIReference'\n        starting_equipment_options:\n          description: List of choices of starting equipment.\n          type: array\n          items:\n            $ref: './combined.yml#/Choice'\n        language_options:\n          $ref: './combined.yml#/Choice'\n        feature:\n          description: Special feature granted to new characters of this background.\n          type: object\n          properties:\n            name:\n              type: string\n            desc:\n              type: array\n              items:\n                type: string\n        personality_traits:\n          $ref: './combined.yml#/Choice'\n        ideals:\n          $ref: './combined.yml#/Choice'\n        bonds:\n          $ref: './combined.yml#/Choice'\n        flaws:\n          $ref: './combined.yml#/Choice'\n"
  },
  {
    "path": "src/swagger/schemas/2014/classes.yml",
    "content": "# TODO: add descriptions to these class-specific fields.\ncs-barbarian:\n  description: Barbarian Class Specific Features\n  type: object\n  properties:\n    rage_count:\n      type: number\n    rage_damage_bonus:\n      type: number\n    brutal_critical_dice:\n      type: number\ncs-bard:\n  description: Bard Class Specific Features\n  type: object\n  properties:\n    bardic_inspiration_dice:\n      type: number\n    song_of_rest_die:\n      type: number\n    magical_secrets_max_5:\n      type: number\n    magical_secrets_max_7:\n      type: number\n    magical_secrets_max_9:\n      type: number\ncs-cleric:\n  description: Cleric Class Specific Features\n  type: object\n  properties:\n    channel_divinity_charges:\n      type: number\n    destroy_undead_cr:\n      type: number\ncs-druid:\n  description: Druid Class Specific Features\n  type: object\n  properties:\n    wild_shape_max_cr:\n      type: number\n    wild_shape_swim:\n      type: boolean\n    wild_shape_fly:\n      type: boolean\ncs-fighter:\n  description: Fighter Class Specific Features\n  type: object\n  properties:\n    action_surges:\n      type: number\n    indomitable_uses:\n      type: number\n    extra_attacks:\n      type: number\ncs-monk:\n  description: Monk Class Specific Features\n  type: object\n  properties:\n    ki_points:\n      type: number\n    unarmored_movement:\n      type: number\n    martial_arts:\n      type: object\n      properties:\n        dice_count:\n          type: number\n        dice_value:\n          type: number\ncs-paladin:\n  description: Paladin Class Specific Features\n  type: object\n  properties:\n    aura_range:\n      type: number\ncs-ranger:\n  description: Bard Ranger Specific Features\n  type: object\n  properties:\n    favored_enemies:\n      type: number\n    favored_terrain:\n      type: number\ncs-rogue:\n  description: Bard Rogue Specific Features\n  type: object\n  properties:\n    sneak_attack:\n      type: object\n      properties:\n        dice_count:\n          type: number\n        dice_value:\n          type: number\ncs-sorcerer:\n  description: Bard Sorcerer Specific Features\n  type: object\n  properties:\n    sorcery_points:\n      type: number\n    metamagic_known:\n      type: number\n    creating_spell_slots:\n      type: array\n      items:\n        type: object\n        properties:\n          spell_slot_level:\n            type: number\n          sorcery_point_cost:\n            type: number\ncs-warlock:\n  description: Bard Warlock Specific Features\n  type: object\n  properties:\n    invocations_known:\n      type: number\n    mystic_arcanum_level_6:\n      type: number\n    mystic_arcanum_level_7:\n      type: number\n    mystic_arcanum_level_8:\n      type: number\n    mystic_arcanum_level_9:\n      type: number\ncs-wizard:\n  description: Wizard Class Specific Features\n  type: object\n  properties:\n    arcane_recover_levels:\n      type: number\n\nclass-model:\n  description: |\n    `Class`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - type: object\n      properties:\n        hit_die:\n          description: 'Hit die of the class. (ex: 12 == 1d12).'\n          type: number\n        class_levels:\n          description: URL of the level resource for the class.\n          type: string\n        multi_classing:\n          $ref: './combined.yml#/Multiclassing'\n        spellcasting:\n          $ref: './combined.yml#/Spellcasting'\n        spells:\n          description: URL of the spell resource list for the class.\n          type: string\n        starting_equipment:\n          description: List of equipment and their quantities all players of the class start with.\n          type: array\n          items:\n            type: object\n            properties:\n              quantity:\n                type: number\n              equipment:\n                $ref: './combined.yml#/APIReference'\n        starting_equipment_options:\n          description: List of choices of starting equipment.\n          type: array\n          items:\n            $ref: './combined.yml#/Choice'\n        proficiency_choices:\n          description: List of choices of starting proficiencies.\n          type: array\n          items:\n            $ref: './combined.yml#/Choice'\n        proficiencies:\n          description: List of starting proficiencies for all new characters of this class.\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\n        saving_throws:\n          description: Saving throws the class is proficient in.\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\n        subclasses:\n          description: List of all possible subclasses this class can specialize in.\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\nclass-level-model:\n  description: |\n    `ClassLevel`\n  type: object\n  properties:\n    index:\n      description: 'Resource index for shorthand searching.'\n      type: string\n    url:\n      description: 'URL of the referenced resource.'\n      type: string\n    level:\n      description: 'The number value for the current level object.'\n      type: number\n    ability_score_bonuses:\n      description: 'Total number of ability score bonuses gained, added from previous levels.'\n      type: number\n    prof_bonus:\n      description: 'Proficiency bonus for this class at the specified level.'\n      type: number\n    features:\n      description: 'Features automatically gained at this level.'\n      type: array\n      items:\n        $ref: './combined.yml#/APIReference'\n    spellcasting:\n      description: 'Summary of spells known at this level.'\n      type: object\n      properties:\n        cantrips_known:\n          type: number\n        spells_known:\n          type: number\n        spell_slots_level_1:\n          type: number\n        spell_slots_level_2:\n          type: number\n        spell_slots_level_3:\n          type: number\n        spell_slots_level_4:\n          type: number\n        spell_slots_level_5:\n          type: number\n        spell_slots_level_6:\n          type: number\n        spell_slots_level_7:\n          type: number\n        spell_slots_level_8:\n          type: number\n        spell_slots_level_9:\n          type: number\n    class_specific:\n      description: 'Class specific information such as dice values for bard songs and number of warlock invocations.'\n      anyOf:\n        - $ref: '#/cs-barbarian'\n        - $ref: '#/cs-bard'\n        - $ref: '#/cs-cleric'\n        - $ref: '#/cs-druid'\n        - $ref: '#/cs-fighter'\n        - $ref: '#/cs-monk'\n        - $ref: '#/cs-paladin'\n        - $ref: '#/cs-ranger'\n        - $ref: '#/cs-rogue'\n        - $ref: '#/cs-sorcerer'\n        - $ref: '#/cs-warlock'\n        - $ref: '#/cs-wizard'\nclass-level-spell-model:\n  description: |\n    `ClassLevelSpell`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - type: object\n      properties:\n        level:\n          type: number\n          description: 'The level of the spell slot used to cast the spell.'\n\nclass-spell-list-model:\n  description: |\n    `ClassSpellList`\n  type: object\n  properties:\n    count:\n      description: 'Total number of resources available.'\n      type: number\n    results:\n      type: array\n      items:\n        $ref: '#/class-level-spell-model'\n"
  },
  {
    "path": "src/swagger/schemas/2014/combined.yml",
    "content": "APIReference:\n  $ref: './common.yml#/api-ref-model'\nAPIReferenceList:\n  $ref: './common.yml#/api-ref-list-model'\nDamage:\n  $ref: './common.yml#/damage-model'\nChoice:\n  $ref: './common.yml#/choice-model'\nAreaOfEffect:\n  $ref: './common.yml#/area-of-effect-model'\nPrerequisite:\n  $ref: './common.yml#/prerequisite-model'\nResourceDescription:\n  $ref: './common.yml#/resource-description-model'\nAbilityScore:\n  $ref: './ability-scores.yml#/ability-score-model'\nAlignment:\n  $ref: './alignments.yml'\nClass:\n  $ref: './classes.yml#/class-model'\nMulticlassing:\n  $ref: './multiclassing.yml'\nSpellcasting:\n  $ref: './spellcasting.yml'\nGear:\n  $ref: './equipment.yml#/gear-model'\nEquipmentPack:\n  $ref: './equipment.yml#/equipment-pack-model'\nEquipmentCategory:\n  $ref: './equipment.yml#/equipment-category-model'\nEquipment:\n  $ref: './equipment.yml#/equipment-model'\nCost:\n  $ref: './common.yml#/cost-model'\nWeapon:\n  $ref: './weapon.yml#/weapon-model'\nArmor:\n  $ref: './armor.yml'\nMagicItem:\n  $ref: './equipment.yml#/magic-item-model'\nDamageType:\n  $ref: './game-mechanics.yml#/damage-type-model'\nDamageAtCharacterLevel:\n  $ref: './spell.yml#/damage-at-character-level-model'\nDamageAtSlotLevel:\n  $ref: './spell.yml#/damage-at-slot-level-model'\nCondition:\n  $ref: './game-mechanics.yml#/condition-model'\nMagicSchool:\n  $ref: './game-mechanics.yml#/magic-school-model'\nSkill:\n  $ref: './skills.yml'\nProficiency:\n  $ref: './proficiencies.yml'\nLanguage:\n  $ref: './language.yml'\nBackground:\n  $ref: './backgrounds.yml#/background-model'\nFeat:\n  $ref: './feats.yml'\nSubclass:\n  $ref: './subclass.yml#/subclass-model'\nSubclassLevel:\n  $ref: './subclass.yml#/subclass-level-model'\nSubclassLevelResource:\n  $ref: './subclass.yml#/subclass-level-resource-model'\nClassLevel:\n  $ref: './classes.yml#/class-level-model'\nFeature:\n  $ref: './features.yml'\nRace:\n  $ref: './races.yml'\nAbilityBonus:\n  $ref: './ability-scores.yml#/AbilityBonus'\nSpell:\n  $ref: './spell.yml#/spell-model'\nSubrace:\n  $ref: './subrace.yml'\nTrait:\n  $ref: './traits.yml#/trait'\nWeaponProperty:\n  $ref: './weapon.yml#/weapon-property-model'\nRule:\n  $ref: './rules.yml#/rule-model'\nRuleSection:\n  $ref: './rules.yml#/rule-section-model'\nMonster:\n  $ref: './monsters.yml#/monster-model'\nMonsterAbility:\n  $ref: './monsters-common.yml#/monster-ability-model'\nMonsterAction:\n  $ref: './monsters-common.yml#/monster-action-model'\nMonsterArmorClass:\n  $ref: './monsters-common.yml#/monster-armor-class-model'\nMonsterAttack:\n  $ref: './monsters-common.yml#/monster-attack-model'\nMonsterMultiAttackAction:\n  $ref: './monsters-common.yml#/monster-multi-attack-action-model'\nMonsterProficiency:\n  $ref: './monsters-common.yml#/monster-proficiency-model'\nMonsterSense:\n  $ref: './monsters-common.yml#/monster-sense-model'\nMonsterSpecialAbility:\n  $ref: './monsters-common.yml#/monster-special-ability-model'\nMonsterSpell:\n  $ref: './monsters-common.yml#/monster-spell-model'\nMonsterSpellcasting:\n  $ref: './monsters-common.yml#/monster-spellcasting-model'\nMonsterUsage:\n  $ref: './monsters-common.yml#/monster-usage-model'\nSpellPrerequisite:\n  $ref: './subclass.yml#/spell-prerequisite'\nerror-response:\n  $ref: './common.yml#/error-response-model'\nDC:\n  $ref: './common.yml#/dc-model'\nOptionSet:\n  $ref: './common.yml#/option-set-model'\nOption:\n  $ref: './common.yml#/option-model'\nClassLevelSpell:\n  $ref: './classes.yml#/class-level-spell-model'\nClassSpellList:\n  $ref: './classes.yml#/class-spell-list-model'\n"
  },
  {
    "path": "src/swagger/schemas/2014/common.yml",
    "content": "api-ref-model:\n  description: |\n    `APIReference`\n  type: object\n  properties:\n    index:\n      description: Resource index for shorthand searching.\n      type: string\n    name:\n      description: 'Name of the referenced resource.'\n      type: string\n    url:\n      description: 'URL of the referenced resource.'\n      type: string\n    updated_at:\n      description: 'Date and time the resource was last updated.'\n      type: string\napi-ref-list-model:\n  description: |\n    `APIReferenceList`\n  type: object\n  properties:\n    count:\n      description: 'Total number of resources available.'\n      type: number\n    results:\n      type: array\n      items:\n        $ref: './combined.yml#/APIReference'\ndamage-model:\n  description: |\n    `Damage`\n  type: object\n  properties:\n    damage_dice:\n      type: string\n    damage_type:\n      $ref: './combined.yml#/APIReference'\ndc-model:\n  description: |\n    `DC`\n  type: object\n  properties:\n    dc_type:\n      $ref: './combined.yml#/APIReference'\n    dc_value:\n      description: 'Value to beat'\n      type: number\n    success_type:\n      description: 'Result of a successful save. Can be \\\"none\\\", \\\"half\\\", or \\\"other\\\"'\n      type: string\noption-model:\n  description: |\n    `Option`\n  oneOf:\n    - type: object\n      properties:\n        option_type:\n          description: 'Type of option; determines other attributes.'\n          type: string\n        item:\n          $ref: './combined.yml#/APIReference'\n    - type: object\n      properties:\n        option_type:\n          description: 'Type of option; determines other attributes.'\n          type: string\n        action_name:\n          description: 'The name of the action.'\n          type: string\n        count:\n          description: 'The number of times this action can be repeated if chosen.'\n          type: number\n        type:\n          description: 'For attack options that can be melee, ranged, abilities, or thrown.'\n          type: string\n          enum: [melee, ranged, ability, magic]\n    - type: object\n      properties:\n        option_type:\n          description: 'Type of option; determines other attributes.'\n          type: string\n        items:\n          type: array\n          items:\n            $ref: './combined.yml#/Option'\n    - type: object\n      properties:\n        option_type:\n          description: 'Type of option; determines other attributes.'\n          type: string\n        choice:\n          $ref: './combined.yml#/Choice'\n    - type: object\n      properties:\n        option_type:\n          description: 'Type of option; determines other attributes.'\n          type: string\n        string:\n          description: 'The string.'\n          type: string\n    - type: object\n      properties:\n        option_type:\n          description: 'Type of option; determines other attributes.'\n          type: string\n        desc:\n          description: 'A description of the ideal.'\n          type: string\n        alignments:\n          description: 'A list of alignments of those who might follow the ideal.'\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\n    - type: object\n      properties:\n        option_type:\n          description: 'Type of option; determines other attributes.'\n          type: string\n        count:\n          description: 'Count'\n          type: number\n        of:\n          $ref: './combined.yml#/APIReference'\n    - type: object\n      properties:\n        option_type:\n          description: 'Type of option; determines other attributes.'\n          type: string\n        ability_score:\n          $ref: './combined.yml#/APIReference'\n        minimum_score:\n          description: 'The minimum score required to satisfy the prerequisite.'\n          type: number\n    - type: object\n      properties:\n        option_type:\n          description: 'Type of option; determines other attributes.'\n          type: string\n        ability_score:\n          $ref: './combined.yml#/APIReference'\n        bonus:\n          description: 'The bonus being applied to the ability score'\n          type: number\n    - type: object\n      properties:\n        option_type:\n          description: 'Type of option; determines other attributes.'\n          type: string\n        name:\n          description: 'Name of the breath'\n          type: string\n        dc:\n          $ref: './combined.yml#/DC'\n        damage:\n          description: 'Damage dealt by the breath attack, if any.'\n          type: array\n          items:\n            $ref: './combined.yml#/Damage'\n    - type: object\n      properties:\n        option_type:\n          description: 'Type of option; determines other attributes.'\n          type: string\n        damage_type:\n          $ref: './combined.yml#/APIReference'\n        damage_dice:\n          description: 'Damage expressed in dice (e.g. \"13d6\").'\n          type: string\n        notes:\n          description: 'Information regarding the damage.'\n          type: string\noption-set-model:\n  description: |\n    `Option Set`\n  oneOf:\n    - type: object\n      properties:\n        option_set_type:\n          description: 'Type of option set; determines other attributes.'\n          type: string\n        options_array:\n          description: 'Array of options to choose from.'\n          type: array\n          items:\n            $ref: './combined.yml#/Option'\n    - type: object\n      properties:\n        option_set_type:\n          description: 'Type of option set; determines other attributes.'\n          type: string\n        equipment_category:\n          $ref: './combined.yml#/APIReference'\n    - type: object\n      properties:\n        option_set_type:\n          description: 'Type of option set; determines other attributes.'\n          type: string\n        resource_list_url:\n          description: 'A reference (by URL) to a collection in the database.'\n          type: string\nchoice-model:\n  description: |\n    `Choice`\n  type: object\n  properties:\n    desc:\n      description: 'Description of the choice to be made.'\n      type: string\n    choose:\n      description: 'Number of items to pick from the list.'\n      type: number\n    type:\n      description: 'Type of the resources to choose from.'\n      type: string\n    from:\n      $ref: './combined.yml#/OptionSet'\ncost-model:\n  description: |\n    `Cost`\n  type: object\n  properties:\n    quantity:\n      description: 'Numerical amount of coins.'\n      type: number\n    unit:\n      description: 'Unit of coinage.'\n      type: string\nprerequisite-model:\n  description: |\n    `Prerequisite`\n  type: object\n  properties:\n    ability_score:\n      allOf:\n        - $ref: './combined.yml#/APIReference'\n    minimum_score:\n      description: 'Minimum score to meet the prerequisite.'\n      type: number\nresource-description-model:\n  type: object\n  properties:\n    desc:\n      description: 'Description of the resource.'\n      type: array\n      items:\n        type: string\nerror-response-model:\n  type: object\n  properties:\n    error:\n      type: string\n  required:\n    - error\narea-of-effect-model:\n  type: object\n  properties:\n    size:\n      type: number\n    type:\n      type: string\n      enum: [sphere, cone, cylinder, line, cube]\n"
  },
  {
    "path": "src/swagger/schemas/2014/equipment.yml",
    "content": "equipment-model:\n  description: |\n    `Equipment`\n  anyOf:\n    - $ref: './combined.yml#/Weapon'\n    - $ref: './combined.yml#/Armor'\n    - $ref: './combined.yml#/Gear'\n    - $ref: './combined.yml#/EquipmentPack'\nequipment-category-model:\n  description: |\n    `EquipmentCategory`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - type: object\n      properties:\n        image:\n          description: 'The image url of the magic item.'\n          type: string\n        equipment:\n          description: 'A list of the equipment that falls into this category.'\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\ngear-model:\n  description: |\n    `Gear`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - $ref: './combined.yml#/ResourceDescription'\n    - type: object\n      properties:\n        image:\n          description: 'The image url of the gear.'\n          type: string\n        equipment_category:\n          $ref: './combined.yml#/APIReference'\n        gear_category:\n          $ref: './combined.yml#/APIReference'\n        cost:\n          $ref: './combined.yml#/Cost'\n        weight:\n          description: 'How much the equipment weighs.'\n          type: number\nequipment-pack-model:\n  description: |\n    `EquipmentPack`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - $ref: './combined.yml#/ResourceDescription'\n    - type: object\n      properties:\n        image:\n          description: 'The image url of the equipment pack.'\n          type: string\n        equipment_category:\n          $ref: './combined.yml#/APIReference'\n        gear_category:\n          $ref: './combined.yml#/APIReference'\n        cost:\n          $ref: './combined.yml#/Cost'\n        contents:\n          description: 'The list of adventuring gear in the pack.'\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\nmagic-item-model:\n  description: |\n    `MagicItem`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - $ref: './combined.yml#/ResourceDescription'\n    - type: object\n      properties:\n        image:\n          description: 'The image url of the magic item.'\n          type: string\n        equipment_category:\n          $ref: './combined.yml#/APIReference'\n        rarity:\n          type: object\n          properties:\n            name:\n              description: 'The rarity of the item.'\n              type: string\n              enum:\n                - Varies\n                - Common\n                - Uncommon\n                - Rare\n                - Very Rare\n                - Legendary\n                - Artifact\n        variants:\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\n        variant:\n          description: 'Whether this is a variant or not'\n          type: boolean\n"
  },
  {
    "path": "src/swagger/schemas/2014/feats.yml",
    "content": "description: |\n  `Feat`\nallOf:\n  - $ref: './combined.yml#/APIReference'\n  - $ref: './combined.yml#/ResourceDescription'\n  - type: object\n    properties:\n      prerequisites:\n        description: 'An object of APIReferences to ability scores and minimum scores.'\n        type: array\n        items:\n          $ref: './combined.yml#/Prerequisite'\n"
  },
  {
    "path": "src/swagger/schemas/2014/features.yml",
    "content": "description: |\n  `Feature`\nallOf:\n  - $ref: './combined.yml#/APIReference'\n  - $ref: './combined.yml#/ResourceDescription'\n  - type: object\n    properties:\n      level:\n        description: 'The level this feature is gained.'\n        type: number\n      class:\n        $ref: './combined.yml#/APIReference'\n      subclass:\n        $ref: './combined.yml#/APIReference'\n      parent:\n        $ref: './combined.yml#/APIReference'\n      prerequisites:\n        description: 'The prerequisites for this feature.'\n        type: array\n        items:\n          anyOf:\n            - type: object\n              properties:\n                type:\n                  type: string\n                level:\n                  type: number\n            - type: object\n              properties:\n                type:\n                  type: string\n                feature:\n                  type: string\n            - type: object\n              properties:\n                type:\n                  type: string\n                spell:\n                  type: string\n        example:\n          - type: 'level'\n            level: 3\n          - type: 'feature'\n            feature: 'martial-archetype'\n          - type: 'spell'\n            spell: 'shield'\n      feature_specific:\n        description: 'Information specific to this feature.'\n        type: object\n        properties:\n          subfeature_options:\n            $ref: './combined.yml#/Choice'\n          expertise_options:\n            $ref: './combined.yml#/Choice'\n          terrain_type_options:\n            $ref: './combined.yml#/Choice'\n          enemy_type_options:\n            $ref: './combined.yml#/Choice'\n          invocations:\n            type: array\n            items:\n              $ref: './combined.yml#/APIReference'\n        additionalProperties: true\n"
  },
  {
    "path": "src/swagger/schemas/2014/game-mechanics.yml",
    "content": "damage-type-model:\n  description: |\n    `DamageType`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - $ref: './combined.yml#/ResourceDescription'\ncondition-model:\n  description: |\n    `Condition`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - $ref: './combined.yml#/ResourceDescription'\nmagic-school-model:\n  description: |\n    `MagicSchool`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - type: object\n      properties:\n        desc:\n          description: Brief description of the resource.\n          type: string\n"
  },
  {
    "path": "src/swagger/schemas/2014/language.yml",
    "content": "description: |\n  `Language`\nallOf:\n  - $ref: './combined.yml#/APIReference'\n  - type: object\n    properties:\n      desc:\n        description: 'Brief description of the language.'\n        type: string\n      type:\n        type: string\n        enum: [Standard, Exotic]\n      script:\n        description: 'Script used for writing in the language.'\n        type: string\n      typical_speakers:\n        description: 'List of races that tend to speak the language.'\n        type: array\n        items:\n          type: string\n"
  },
  {
    "path": "src/swagger/schemas/2014/monsters-common.yml",
    "content": "monster-attack-model:\n  type: object\n  properties:\n    name:\n      type: string\n    dc:\n      $ref: './combined.yml#/DC'\n    damage:\n      $ref: './combined.yml#/Damage'\n\nmonster-ability-model:\n  description: |\n    `Monster Ability`\n  type: object\n  properties:\n    charisma:\n      description: \"A monster's ability to charm or intimidate a player.\"\n      type: number\n    constitution:\n      description: How sturdy a monster is.\"\n      type: number\n    dexterity:\n      description: \"The monster's ability for swift movement or stealth\"\n      type: number\n    intelligence:\n      description: \"The monster's ability to outsmart a player.\"\n      type: number\n    strength:\n      description: 'How hard a monster can hit a player.'\n      type: number\n    wisdom:\n      description: \"A monster's ability to ascertain the player's plan.\"\n      type: number\n\nmonster-action-model:\n  description: Action available to a `Monster` in addition to the standard creature actions.\n  type: object\n  properties:\n    name:\n      type: string\n    desc:\n      type: string\n    action_options:\n      $ref: './combined.yml#/Choice'\n    actions:\n      type: array\n      items:\n        $ref: './combined.yml#/MonsterMultiAttackAction'\n    options:\n      $ref: './combined.yml#/Choice'\n    multiattack_type:\n      type: string\n    attack_bonus:\n      type: number\n    dc:\n      $ref: './combined.yml#/DC'\n    attacks:\n      type: array\n      items:\n        $ref: './combined.yml#/MonsterAttack'\n    damage:\n      type: array\n      items:\n        $ref: './combined.yml#/Damage'\n\nmonster-armor-class-model:\n  description: The armor class of a monster.\n  type: object\n  oneOf:\n    - type: object\n      properties:\n        type:\n          type: string\n          enum: [dex]\n        value:\n          type: number\n        desc:\n          type: string\n    - type: object\n      properties:\n        type:\n          type: string\n          enum: [natural]\n        value:\n          type: number\n        desc:\n          type: string\n    - type: object\n      properties:\n        type:\n          type: string\n          enum: [armor]\n        value:\n          type: number\n        armor:\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\n        desc:\n          type: string\n    - type: object\n      properties:\n        type:\n          type: string\n          enum: [spell]\n        value:\n          type: number\n        spell:\n          $ref: './combined.yml#/APIReference'\n        desc:\n          type: string\n    - type: object\n      properties:\n        type:\n          type: string\n          enum: [condition]\n        value:\n          type: number\n        condition:\n          $ref: './combined.yml#/APIReference'\n        desc:\n          type: string\n\nmonster-multi-attack-action-model:\n  type: object\n  properties:\n    action_name:\n      type: string\n    count:\n      type: number\n    type:\n      type: string\n      enum: [melee, ranged, ability, magic]\n\nmonster-proficiency-model:\n  type: object\n  properties:\n    value:\n      type: number\n    proficiency:\n      $ref: './combined.yml#/APIReference'\n\nmonster-sense-model:\n  type: object\n  properties:\n    passive_perception:\n      description: The monster's passive perception (wisdom) score.\n      type: number\n    blindsight:\n      description: A monster with blindsight can perceive its surroundings without relying on sight, within a specific radius.\n      type: string\n    darkvision:\n      description: A monster with darkvision can see in the dark within a specific radius.\n      type: string\n    tremorsense:\n      description: A monster with tremorsense can detect and pinpoint the origin of vibrations within a specific radius, provided that the monster and the source of the vibrations are in contact with the same ground or substance.\n      type: string\n    truesight:\n      description: A monster with truesight can, out to a specific range, see in normal and magical darkness, see invisible creatures and objects, automatically detect visual illusions and succeed on saving throws against them, and perceive the original form of a shapechanger or a creature that is transformed by magic. Furthermore, the monster can see into the Ethereal Plane within the same range.\n      type: string\n\nmonster-special-ability-model:\n  type: object\n  properties:\n    name:\n      type: string\n    desc:\n      type: string\n    attack_bonus:\n      type: number\n    damage:\n      $ref: './combined.yml#/Damage'\n    dc:\n      $ref: './combined.yml#/DC'\n    spellcasting:\n      $ref: './combined.yml#/MonsterSpellcasting'\n    usage:\n      $ref: './combined.yml#/MonsterUsage'\n\nmonster-spell-model:\n  type: object\n  properties:\n    name:\n      type: string\n    level:\n      type: number\n    url:\n      type: string\n    usage:\n      $ref: './combined.yml#/MonsterUsage'\n\nmonster-spellcasting-model:\n  type: object\n  properties:\n    ability:\n      $ref: './combined.yml#/APIReference'\n    dc:\n      type: number\n    modifier:\n      type: number\n    components_required:\n      type: array\n      items:\n        type: string\n    school:\n      type: string\n    slots:\n      type: object\n      additionalProperties:\n        type: number\n    spells:\n      type: array\n      items:\n        $ref: './combined.yml#/MonsterSpell'\n\nmonster-usage-model:\n  type: object\n  properties:\n    type:\n      type: string\n      enum:\n        - at will\n        - per day\n        - recharge after rest\n        - recharge on roll\n    rest_types:\n      type: array\n      items:\n        type: string\n    times:\n      type: number\n"
  },
  {
    "path": "src/swagger/schemas/2014/monsters.yml",
    "content": "monster-model:\n  description: |\n    `Monster`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - $ref: './combined.yml#/ResourceDescription'\n    - $ref: './combined.yml#/MonsterAbility'\n    - type: object\n      properties:\n        image:\n          description: 'The image url of the monster.'\n          type: string\n        size:\n          description: The size of the monster ranging from Tiny to Gargantuan.\"\n          type: string\n          enum:\n            - Tiny\n            - Small\n            - Medium\n            - Large\n            - Huge\n            - Gargantuan\n        type:\n          description: 'The type of monster.'\n          type: string\n        subtype:\n          description: The sub-category of a monster used for classification of monsters.\"\n          type: string\n        alignment:\n          description: \"A creature's general moral and personal attitudes.\"\n          type: string\n        armor_class:\n          description: 'The difficulty for a player to successfully deal damage to a monster.'\n          type: array\n          items:\n            $ref: './combined.yml#/MonsterArmorClass'\n        hit_points:\n          description: 'The hit points of a monster determine how much damage it is able to take before it can be defeated.'\n          type: number\n        hit_dice:\n          description: 'The hit die of a monster can be used to make a version of the same monster whose hit points are determined by the roll of the die. For example: A monster with 2d6 would have its hit points determine by rolling a 6 sided die twice.'\n          type: string\n        hit_points_roll:\n          description: \"The roll for determining a monster's hit points, which consists of the hit dice (e.g. 18d10) and the modifier determined by its Constitution (e.g. +36). For example, 18d10+36\"\n          type: string\n        actions:\n          description: 'A list of actions that are available to the monster to take during combat.'\n          type: array\n          items:\n            $ref: './combined.yml#/MonsterAction'\n        legendary_actions:\n          description: 'A list of legendary actions that are available to the monster to take during combat.'\n          type: array\n          items:\n            $ref: './combined.yml#/MonsterAction'\n        challenge_rating:\n          description: \"A monster's challenge rating is a guideline number that says when a monster becomes an appropriate challenge against the party's average level. For example. A group of 4 players with an average level of 4 would have an appropriate combat challenge against a monster with a challenge rating of 4 but a monster with a challenge rating of 8 against the same group of players would pose a significant threat.\"\n          type: number\n          minimum: 0\n          maximum: 21\n        proficiency_bonus:\n          description: \"A monster's proficiency bonus is the number added to ability checks, saving throws and attack rolls in which the monster is proficient, and is linked to the monster's challenge rating. This bonus has already been included in the monster's stats where applicable.\"\n          type: number\n          minimum: 2\n          maximum: 9\n        condition_immunities:\n          description: 'A list of conditions that a monster is immune to.'\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\n        damage_immunities:\n          description: 'A list of damage types that a monster will take double damage from.'\n          type: array\n          items:\n            type: string\n        damage_resistances:\n          description: 'A list of damage types that a monster will take half damage from.'\n          type: array\n          items:\n            type: string\n        damage_vulnerabilities:\n          description: 'A list of damage types that a monster will take double damage from.'\n          type: array\n          items:\n            type: string\n        forms:\n          description: 'List of other related monster entries that are of the same form. Only applicable to Lycanthropes that have multiple forms.'\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\n        languages:\n          description: 'The languages a monster is able to speak.'\n          type: string\n        proficiencies:\n          description: 'A list of proficiencies of a monster.'\n          type: array\n          items:\n            $ref: './combined.yml#/MonsterProficiency'\n        reactions:\n          description: 'A list of reactions that is available to the monster to take during combat.'\n          type: array\n          items:\n            $ref: './combined.yml#/MonsterAction'\n        senses:\n          description: 'Monsters typically have a passive perception but they might also have other senses to detect players.'\n          allOf:\n            - $ref: './combined.yml#/MonsterSense'\n        special_abilities:\n          description: \"A list of the monster's special abilities.\"\n          type: array\n          items:\n            $ref: './combined.yml#/MonsterSpecialAbility'\n        speed:\n          description: 'Speed for a monster determines how fast it can move per turn.'\n          type: object\n          properties:\n            walk:\n              description: All creatures have a walking speed, simply called the monster’s speed. Creatures that have no form of ground-based locomotion have a walking speed of 0 feet.\n              type: string\n            burrow:\n              description: A monster that has a burrowing speed can use that speed to move through sand, earth, mud, or ice. A monster can’t burrow through solid rock unless it has a special trait that allows it to do so.\n              type: string\n            climb:\n              description: A monster that has a climbing speed can use all or part of its movement to move on vertical surfaces. The monster doesn’t need to spend extra movement to climb.\n              type: string\n            fly:\n              description: A monster that has a flying speed can use all or part of its movement to fly.\n              type: string\n            swim:\n              description: A monster that has a swimming speed doesn’t need to spend extra movement to swim.\n              type: string\n        xp:\n          description: The number of experience points (XP) a monster is worth is based on its challenge rating.\n          type: number\n"
  },
  {
    "path": "src/swagger/schemas/2014/multiclassing.yml",
    "content": "description: |\n  `Multiclassing`\ntype: object\nproperties:\n  prerequisites:\n    description: List of prerequisites that must be met.\n    type: array\n    items:\n      $ref: './combined.yml#/Prerequisite'\n  prerequisite_options:\n    description: List of choices of prerequisites to meet for.\n    type: array\n    items:\n      $ref: './combined.yml#/Choice'\n  proficiencies:\n    description: 'List of proficiencies available when multiclassing.'\n    type: array\n    items:\n      $ref: './combined.yml#/APIReference'\n  proficiency_choices:\n    description: List of choices of proficiencies that are given when multiclassing.\n    type: array\n    items:\n      $ref: './combined.yml#/Choice'\n"
  },
  {
    "path": "src/swagger/schemas/2014/proficiencies.yml",
    "content": "description: |\n  `Proficiency`\nallOf:\n  - $ref: './combined.yml#/APIReference'\n  - type: object\n    properties:\n      type:\n        description: The general category of the proficiency.\n        type: string\n      classes:\n        description: Classes that start with this proficiency.\n        type: array\n        items:\n          $ref: './combined.yml#/APIReference'\n      races:\n        description: Races that start with this proficiency.\n        type: array\n        items:\n          $ref: './combined.yml#/APIReference'\n      reference:\n        description: |\n          `APIReference` to the full description of the related resource.\n        allOf:\n          - $ref: './combined.yml#/APIReference'\n"
  },
  {
    "path": "src/swagger/schemas/2014/races.yml",
    "content": "description: |\n  `Race`\nallOf:\n  - $ref: './combined.yml#/APIReference'\n  - type: object\n    properties:\n      speed:\n        description: 'Base move speed for this race (in feet per round).'\n        type: number\n      ability_bonuses:\n        description: 'Racial bonuses to ability scores.'\n        type: array\n        items:\n          $ref: './combined.yml#/AbilityBonus'\n      alignment:\n        description: 'Flavor description of likely alignments this race takes.'\n        type: string\n      age:\n        description: 'Flavor description of possible ages for this race.'\n        type: string\n      size:\n        description: 'Size class of this race.'\n        type: string\n      size_description:\n        description: 'Flavor description of height and weight for this race.'\n        type: string\n      languages:\n        description: 'Starting languages for all new characters of this race.'\n        type: array\n        items:\n          $ref: './combined.yml#/APIReference'\n      language_desc:\n        description: 'Flavor description of the languages this race knows.'\n        type: string\n      traits:\n        description: 'Racial traits that provide benefits to its members.'\n        type: array\n        items:\n          $ref: './combined.yml#/APIReference'\n      subraces:\n        description: 'All possible subraces that this race includes.'\n        type: array\n        items:\n          $ref: './combined.yml#/APIReference'\n"
  },
  {
    "path": "src/swagger/schemas/2014/rules.yml",
    "content": "rule-model:\n  description: |\n    `Rule`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - type: object\n      properties:\n        desc:\n          description: Description of the rule.\n          type: string\n        subsections:\n          description: 'List of sections for each subheading underneath the rule in the SRD.'\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\nrule-section-model:\n  description: |\n    `RuleSection`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - type: object\n      properties:\n        desc:\n          description: Description of the rule.\n          type: string\n"
  },
  {
    "path": "src/swagger/schemas/2014/skills.yml",
    "content": "description: |\n  `Skill`\nallOf:\n  - $ref: './combined.yml#/APIReference'\n  - $ref: './combined.yml#/ResourceDescription'\n  - type: object\n    properties:\n      ability_score:\n        $ref: './combined.yml#/APIReference'\n"
  },
  {
    "path": "src/swagger/schemas/2014/spell.yml",
    "content": "spell-model:\n  description: |\n    `Spell`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - $ref: './combined.yml#/ResourceDescription'\n    - type: object\n      properties:\n        higher_level:\n          description: 'List of descriptions for casting the spell at higher levels.'\n          type: array\n          items:\n            type: string\n        range:\n          description: 'Range of the spell, usually expressed in feet.'\n          type: string\n        components:\n          description: |\n            List of shorthand for required components of the spell.\n            V: verbal\n            S: somatic\n            M: material\n          type: array\n          items:\n            type: string\n            enum: [V, S, M]\n        material:\n          description: 'Material component for the spell to be cast.'\n          type: string\n        area_of_effect:\n          $ref: './combined.yml#/AreaOfEffect'\n        ritual:\n          description: 'Determines if a spell can be cast in a 10-min(in-game) ritual.'\n          type: boolean\n        duration:\n          description: 'How long the spell effect lasts.'\n          type: string\n        concentration:\n          description: 'Determines if a spell needs concentration to persist.'\n          type: boolean\n        casting_time:\n          description: 'How long it takes for the spell to activate.'\n          type: string\n        level:\n          description: 'Level of the spell.'\n          type: number\n        attack_type:\n          description: 'Attack type of the spell.'\n          type: string\n        damage:\n          oneOf:\n            - $ref: './combined.yml#/DamageAtCharacterLevel'\n            - $ref: './combined.yml#/DamageAtSlotLevel'\n        school:\n          description: 'Magic school this spell belongs to.'\n          $ref: './combined.yml#/APIReference'\n        classes:\n          description: 'List of classes that are able to learn the spell.'\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\n        subclasses:\n          description: 'List of subclasses that have access to the spell.'\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\ndamage-at-character-level-model:\n  description: |\n    'Spell Damage scaling by character level'\n  type: object\n  properties:\n    damage_at_character_level:\n      description: 'Damage at each character level, keyed by level number.'\n      type: object\n      additionalProperties:\n        type: string\n    damage_type:\n      $ref: './combined.yml#/APIReference'\ndamage-at-slot-level-model:\n  description: |\n    'Spell Damage scaling by spell slot level'\n  type: object\n  properties:\n    damage_at_slot_level:\n      description: 'Damage at each spell slot level, keyed by slot level number.'\n      type: object\n      additionalProperties:\n        type: string\n    damage_type:\n      $ref: './combined.yml#/APIReference'\n"
  },
  {
    "path": "src/swagger/schemas/2014/spellcasting.yml",
    "content": "type: object\ndescription: |\n  `Spellcasting`\nproperties:\n  level:\n    description: Level at which the class can start using its spellcasting abilities.\n    type: number\n  info:\n    description: \"Descriptions of the class' ability to cast spells.\"\n    type: array\n    items:\n      type: object\n      properties:\n        name:\n          description: Feature name.\n          type: string\n        desc:\n          description: Feature description.\n          type: array\n          items:\n            type: string\n  spellcasting_ability:\n    description: Reference to the `AbilityScore` used for spellcasting by the class.\n    allOf:\n      - $ref: './combined.yml#/APIReference'\n"
  },
  {
    "path": "src/swagger/schemas/2014/subclass.yml",
    "content": "spell-prerequisite:\n  description: |\n    `SpellPrerequisite`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - type: object\n      properties:\n        type:\n          description: The type of prerequisite.\n          type: string\n\nsubclass-model:\n  description: |\n    `Subclass`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - $ref: './combined.yml#/ResourceDescription'\n    - type: object\n      properties:\n        class:\n          $ref: './combined.yml#/APIReference'\n        subclass_flavor:\n          description: Lore-friendly flavor text for a classes respective subclass.\n          type: string\n        subclass_levels:\n          description: Resource url that shows the subclass level progression.\n          type: string\n        spells:\n          type: array\n          items:\n            type: object\n            properties:\n              prerequisites:\n                type: array\n                items:\n                  $ref: './combined.yml#/SpellPrerequisite'\n              spell:\n                $ref: './combined.yml#/APIReference'\n\nsubclass-level-resource-model:\n  type: object\n  properties:\n    index:\n      type: string\n    url:\n      type: string\n    level:\n      type: number\n    features:\n      type: array\n      items:\n        $ref: './combined.yml#/APIReference'\n    class:\n      $ref: './combined.yml#/APIReference'\n    subclass:\n      $ref: './combined.yml#/APIReference'\nsubclass-level-model:\n  description: |\n    `SubclassLevel`\n  type: object\n  properties:\n    index:\n      description: 'Resource index for shorthand searching.'\n      type: string\n    url:\n      description: 'URL of the referenced resource.'\n      type: string\n    level:\n      description: 'Number value for the current level object.'\n      type: number\n    ability_score_bonuses:\n      description: 'Total number of ability score bonuses gained, added from previous levels.'\n      type: number\n    prof_bonus:\n      description: 'Proficiency bonus for this class at the specified level.'\n      type: number\n    features:\n      description: List of features gained at this level.\n      type: array\n      items:\n        $ref: './combined.yml#/APIReference'\n    spellcasting:\n      description: 'Summary of spells known at this level.'\n      type: object\n      properties:\n        cantrips_known:\n          type: number\n        spells_known:\n          type: number\n        spell_slots_level_1:\n          type: number\n        spell_slots_level_2:\n          type: number\n        spell_slots_level_3:\n          type: number\n        spell_slots_level_4:\n          type: number\n        spell_slots_level_5:\n          type: number\n        spell_slots_level_6:\n          type: number\n        spell_slots_level_7:\n          type: number\n        spell_slots_level_8:\n          type: number\n        spell_slots_level_9:\n          type: number\n    classspecific:\n      description: 'Class specific information such as dice values for bard songs and number of warlock invocations.'\n      additionalProperties: {}\n"
  },
  {
    "path": "src/swagger/schemas/2014/subrace.yml",
    "content": "description: |\n  `Subrace`\nallOf:\n  - $ref: './combined.yml#/APIReference'\n  - type: object\n    properties:\n      desc:\n        description: Description of the subrace.\n        type: string\n      race:\n        description: 'Parent race for the subrace.'\n        allOf:\n          - $ref: './combined.yml#/APIReference'\n      ability_bonuses:\n        description: 'Additional ability bonuses for the subrace.'\n        type: array\n        items:\n          $ref: './combined.yml#/AbilityBonus'\n      racial_traits:\n        description: 'List of traits that for the subrace.'\n        type: array\n        items:\n          $ref: './combined.yml#/APIReference'\n"
  },
  {
    "path": "src/swagger/schemas/2014/traits.yml",
    "content": "trait-damage:\n  type: object\n  properties:\n    damage-type:\n      description: A damage type associated with this trait.\n      allOf:\n        - $ref: './combined.yml#/APIReference'\n    breath-weapon:\n      description: The breath weapon action associated with a draconic ancestry.\n      type: object\n      properties:\n        name:\n          type: string\n        desc:\n          type: string\n        area_of_effect:\n          $ref: './combined.yml#/AreaOfEffect'\n        damage:\n          type: object\n          properties:\n            damage_at_character_level:\n              type: object\n              additionalProperties:\n                type: string\n            damage_type:\n              allOf:\n                - $ref: './combined.yml#/APIReference'\n        dc:\n          $ref: './combined.yml#/DC'\n        usage:\n          description: Description of the usage constraints of this action.\n          type: object\n          properties:\n            times:\n              type: number\n            type:\n              type: string\ntrait:\n  description: |\n    `Trait`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - $ref: './combined.yml#/ResourceDescription'\n    - type: object\n      properties:\n        races:\n          description: 'List of `Races` that have access to the trait.'\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\n        subraces:\n          description: 'List of `Subraces` that have access to the trait.'\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\n        proficiencies:\n          description: 'List of `Proficiencies` this trait grants.'\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\n        proficiency_choices:\n          $ref: './combined.yml#/Choice'\n        language_options:\n          $ref: './combined.yml#/Choice'\n        trait_specific:\n          description: 'Information specific to this trait'\n          oneOf:\n            - $ref: './combined.yml#/Choice'\n            - $ref: './combined.yml#/Choice'\n            - $ref: '#/trait-damage'\n"
  },
  {
    "path": "src/swagger/schemas/2014/weapon.yml",
    "content": "weapon-model:\n  description: |\n    `Weapon`\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - $ref: './combined.yml#/ResourceDescription'\n    - type: object\n      properties:\n        equipment_category:\n          $ref: './combined.yml#/APIReference'\n        weapon_category:\n          description: 'The category of weapon this falls into.'\n          type: string\n        weapon_range:\n          description: 'Whether this is a Melee or Ranged weapon.'\n          type: string\n        category_range:\n          description: 'A combination of weapon_category and weapon_range.'\n          type: string\n        range:\n          type: object\n          properties:\n            normal:\n              description: \"The weapon's normal range in feet.\"\n              type: number\n            long:\n              description: \"The weapon's long range in feet.\"\n              type: number\n        damage:\n          $ref: './combined.yml#/Damage'\n        two_handed_damage:\n          $ref: './combined.yml#/Damage'\n        properties:\n          description: 'A list of the properties this weapon has.'\n          type: array\n          items:\n            $ref: './combined.yml#/APIReference'\n        cost:\n          $ref: './combined.yml#/Cost'\n        weight:\n          description: 'How much the equipment weighs.'\n          type: number\nweapon-property-model:\n  description: WeaponProperty\n  allOf:\n    - $ref: './combined.yml#/APIReference'\n    - $ref: './combined.yml#/ResourceDescription'\n"
  },
  {
    "path": "src/swagger/schemas/2024/.keepme",
    "content": ""
  },
  {
    "path": "src/swagger/swagger.yml",
    "content": "openapi: 3.0.1\ninfo:\n  title: D&D 5e API\n  description: |\n    # Introduction\n\n    Welcome to the dnd5eapi, the Dungeons & Dragons 5th Edition API!\n    This documentation should help you familiarize yourself with the resources\n    available and how to consume them with HTTP requests. Read through the getting\n    started section before you dive in. Most of your problems should be solved\n    just by reading through it.\n\n    ## Getting Started\n\n    Let's make our first API request to the D&D 5th Edition API!\n\n    Open up a terminal and use [curl](http://curl.haxx.se/) or [httpie](http://httpie.org/)\n    to make an API request for a resource. You can also scroll through the\n    definitions below and send requests directly from the endpoint documentation!\n\n    For example, if you paste and run this `curl` command:\n    ```bash\n    curl -X GET \"https://www.dnd5eapi.co/api/2014/ability-scores/cha\" -H \"Accept: application/json\"\n    ```\n\n    We should see a result containing details about the Charisma ability score:\n    ```bash\n    {\n      \"index\": \"cha\",\n      \"name\": \"CHA\",\n      \"full_name\": \"Charisma\",\n      \"desc\": [\n        \"Charisma measures your ability to interact effectively with others. It\n          includes such factors as confidence and eloquence, and it can represent\n          a charming or commanding personality.\",\n        \"A Charisma check might arise when you try to influence or entertain\n          others, when you try to make an impression or tell a convincing lie,\n          or when you are navigating a tricky social situation. The Deception,\n          Intimidation, Performance, and Persuasion skills reflect aptitude in\n          certain kinds of Charisma checks.\"\n      ],\n      \"skills\": [\n        {\n          \"name\": \"Deception\",\n          \"index\": \"deception\",\n          \"url\": \"/api/2014/skills/deception\"\n        },\n        {\n          \"name\": \"Intimidation\",\n          \"index\": \"intimidation\",\n          \"url\": \"/api/2014/skills/intimidation\"\n        },\n        {\n          \"name\": \"Performance\",\n          \"index\": \"performance\",\n          \"url\": \"/api/2014/skills/performance\"\n        },\n        {\n          \"name\": \"Persuasion\",\n          \"index\": \"persuasion\",\n          \"url\": \"/api/2014/skills/persuasion\"\n        }\n      ],\n      \"url\": \"/api/2014/ability-scores/cha\"\n    }\n    ```\n\n    ## Authentication\n\n    The dnd5eapi is a completely open API. No authentication is required to query\n    and get data. This also means that we've limited what you can do to just\n    `GET`-ing the data. If you find a mistake in the data, feel free to\n    [message us](https://discord.gg/TQuYTv7).\n\n    ## GraphQL\n\n    This API supports [GraphQL](https://graphql.org/). The GraphQL URL for this API\n    is `https://www.dnd5eapi.co/graphql`. Most of your questions regarding the GraphQL schema can be answered\n    by querying the endpoint with the Apollo sandbox explorer.\n\n    ## Schemas\n\n    Definitions of all schemas will be accessible in a future update. Two of the most common schemas are described here.\n\n    ### `APIReference`\n    Represents a minimal representation of a resource. The detailed representation of the referenced resource can be retrieved by making a request to the referenced `URL`.\n    ```\n    APIReference {\n      index     string\n      name      string\n      url       string\n    }\n    ```\n    <hr>\n\n    ### `DC`\n    Represents a difficulty check.\n    ```\n    DC {\n      dc_type       APIReference\n      dc_value      number\n      success_type  \"none\" | \"half\" | \"other\"\n    }\n    ```\n    <hr>\n\n    ### `Damage`\n    Represents damage.\n    ```\n    Damage {\n      damage_type     APIReference\n      damage_dice     string\n    }\n    ```\n    <hr>\n\n    ### `Choice`\n    Represents a choice made by a player. Commonly seen related to decisions made during character creation or combat (e.g.: the description of the cleric class, under **Proficiencies**, states \"Skills: Choose two from\tHistory, Insight, Medicine, Persuasion, and\tReligion\" [[SRD p15]](https://media.wizards.com/2016/downloads/DND/SRD-OGL_V5.1.pdf#page=15))\n    ```\n    Choice {\n      desc      string\n      choose    number\n      type      string\n      from      OptionSet\n    }\n    ```\n    <hr>\n\n    ### `OptionSet`\n    The OptionSet structure provides the options to be chosen from, or sufficient data to fetch and interpret the options. All OptionSets have an `option_set_type` attribute that indicates the structure of the object that contains the options. The possible values are `options_array`, `equipment_category`, and `reference_list`. Other attributes on the OptionSet depend on the value of this attribute.\n    - `options_array`\n      - `options` (array): An array of Option objects. Each item in the array represents an option that can be chosen.\n    - `equipment_category`\n      - `equipment_category` (APIReference): A reference to an EquipmentCategory. Each item in the EquipmentCategory's `equipment` array represents one option that can be chosen.\n    - `resource_list`\n      - `resource_list_url` (string): A reference (by URL) to a collection in the database. The URL may include query parameters. Each item in the resulting ResourceList's `results` array represents one option that can be chosen.\n    <hr>\n\n    ### `Option`\n    When the options are given in an `options_array`, each item in the array inherits from the Option structure. All Options have an `option_type` attribute that indicates the structure of the option. The value of this attribute indicates how the option should be handled, and each type has different attributes. The possible values and their corresponding attributes are listed below.\n    - `reference` - A terminal option. Contains a reference to a Document that can be added to the list of options chosen.\n      - `item` (APIReference): A reference to the chosen item.\n    - `action` - A terminal option. Contains information describing an action, for use within Multiattack actions.\n      - `action_name` (string): The name of the action, according to its `name` attribute.\n      - `count` (number | string): The number of times this action can be repeated if this option is chosen.\n      - `type` (string = `\"melee\" | \"ranged\" | \"ability\" | \"magic\"`, optional): For attack actions that can be either melee, ranged, abilities, or magic.\n    - `multiple` - When this option is chosen, all of its child options are chosen, and must be resolved the same way as a normal option.\n      - `items` (array): An array of Option objects. All of them must be taken if the option is chosen.\n    - `choice` - A nested choice. If this option is chosen, the Choice structure contained within must be resolved like a normal Choice structure, and the results are the chosen options.\n      - `choice` (Choice): The Choice to resolve.\n    - `string` - A terminal option. Contains a reference to a string.\n      - `string` (string): The string.\n    - `ideal` - A terminal option. Contains information about an ideal.\n      - `desc` (string): A description of the ideal.\n      - `alignments` (ApiReference[]): A list of alignments of those who might follow the ideal.\n    - `counted_reference` - A terminal option. Contains a reference to something else in the API along with a count.\n      - `count` (number): Count.\n      - `of` (ApiReference): Thing being referenced.\n    - `score_prerequisite` - A terminal option. Contains a reference to an ability score and a minimum score.\n      - `ability_score` (ApiReference): Ability score being referenced.\n      - `minimum_score` (number): The minimum score required to satisfy the prerequisite.\n    - `ability_bonus` - A terminal option. Contains a reference to an ability score and a bonus\n      - `ability_score` (ApiReference): Ability score being referenced\n      - `bonus` (number): The bonus being applied to the ability score\n    - `breath` - A terminal option: Contains a reference to information about a breath attack.\n      - `name` (string): Name of the breath.\n      - `dc` (DC): Difficulty check of the breath attack.\n      - `damage` ([Damage]): Damage dealt by the breath attack, if any.\n    - `damage` - A terminal option. Contains information about damage.\n      - `damage_type` (ApiReference): Reference to type of damage.\n      - `damage_dice` (string): Damage expressed in dice (e.g. \"13d6\").\n      - `notes` (string): Information regarding the damage.\n\n    ## FAQ\n\n    ### What is the SRD?\n    The SRD, or Systems Reference Document, contains guidelines for publishing content under the OGL. This allows for some of the data for D&D 5e to be open source. The API only covers data that can be found in the SRD. [Here's a link to the full text of the SRD.](https://media.wizards.com/2016/downloads/DND/SRD-OGL_V5.1.pdf)\n\n    ### What is the OGL?\n    The Open Game License (OGL) is a public copyright license by Wizards of the Coast that may be used by tabletop role-playing game developers to grant permission to modify, copy, and redistribute some of the content designed for their games, notably game mechanics. However, they must share-alike copies and derivative works. [More information about the OGL can be found here.](https://en.wikipedia.org/wiki/Open_Game_License)\n\n    ### A monster, spell, subclass, etc. is missing from the API / Database. Can I add it?\n    Please check if the data is within the SRD. If it is, feel free to open an issue or PR to add it yourself. Otherwise, due to legal reasons, we cannot add it.\n\n    ### Can this API be self hosted?\n    Yes it can! You can also host the data yourself if you don't want to use the API at all. You can also make changes and add extra data if you like. However, it is up to you to merge in new changes to the data and API.\n\n    #### Can I publish is on <insert platform>? Is this free use?\n    Yes, you can. The API itself is under the [MIT license](https://opensource.org/licenses/MIT), and the underlying data accessible via the API is supported under the SRD and OGL.\n\n    # Status Page\n\n    The status page for the API can be found here: https://5e-bits.github.io/dnd-uptime/\n\n    # Chat\n\n    Come hang out with us [on Discord](https://discord.gg/TQuYTv7)!\n\n    # Contribute\n\n    This API is built from two repositories.\n      - The repo containing the data lives here: https://github.com/bagelbits/5e-database\n      - The repo with the API implementation lives here: https://github.com/bagelbits/5e-srd-api\n\n    This is a evolving API and having fresh ideas are always welcome! You can\n    open an issue in either repo, open a PR for changes, or just discuss with\n    other users in this discord.\n  version: '0.1'\n  license:\n    name: MIT License\n    url: 'https://github.com/5e-bits/5e-srd-api/blob/main/LICENSE.md'\n  contact:\n    name: 5eBits\n    url: https://github.com/5e-bits\nservers:\n  - url: https://www.dnd5eapi.co\n    description: Production\n  - url: http://127.0.0.1:3000\n    description: Local Development\ntags:\n  - name: Common\n    description: General API endpoints and utilities\n  - name: Character Data\n    description: Endpoints related to character creation and management data\n\npaths:\n  /api/2014:\n    $ref: './paths/2014/combined.yml#/base'\n  /api/2014/{endpoint}:\n    $ref: './paths/2014/combined.yml#/list'\n  /api/2014/ability-scores/{index}:\n    $ref: './paths/2014/combined.yml#/ability-scores'\n  /api/2014/alignments/{index}:\n    $ref: './paths/2014/combined.yml#/alignments'\n  /api/2014/backgrounds/{index}:\n    $ref: './paths/2014/combined.yml#/backgrounds'\n  /api/2014/classes/{index}:\n    $ref: './paths/2014/combined.yml#/classes'\n  /api/2014/classes/{index}/subclasses:\n    $ref: './paths/2014/combined.yml#/class-subclass'\n  /api/2014/classes/{index}/spells:\n    $ref: './paths/2014/combined.yml#/class-spells'\n  /api/2014/classes/{index}/spellcasting:\n    $ref: './paths/2014/combined.yml#/class-spellcasting'\n  /api/2014/classes/{index}/features:\n    $ref: './paths/2014/combined.yml#/class-features'\n  /api/2014/classes/{index}/proficiencies:\n    $ref: './paths/2014/combined.yml#/class-proficiencies'\n  /api/2014/classes/{index}/multi-classing:\n    $ref: './paths/2014/combined.yml#/class-multi-classing'\n  /api/2014/classes/{index}/levels:\n    $ref: './paths/2014/combined.yml#/class-levels'\n  /api/2014/classes/{index}/levels/{class_level}:\n    $ref: './paths/2014/combined.yml#/class-level'\n  /api/2014/classes/{index}/levels/{class_level}/features:\n    $ref: './paths/2014/combined.yml#/class-level-features'\n  /api/2014/classes/{index}/levels/{spell_level}/spells:\n    $ref: './paths/2014/combined.yml#/class-spell-level-spells'\n  /api/2014/conditions/{index}:\n    $ref: './paths/2014/combined.yml#/conditions'\n  /api/2014/damage-types/{index}:\n    $ref: './paths/2014/combined.yml#/damage-types'\n  /api/2014/equipment/{index}:\n    $ref: './paths/2014/combined.yml#/equipment'\n  /api/2014/equipment-categories/{index}:\n    $ref: './paths/2014/combined.yml#/equipment-categories'\n  /api/2014/feats/{index}:\n    $ref: './paths/2014/combined.yml#/feats'\n  /api/2014/features/{index}:\n    $ref: './paths/2014/combined.yml#/features'\n  /api/2014/languages/{index}:\n    $ref: './paths/2014/combined.yml#/languages'\n  /api/2014/magic-items/{index}:\n    $ref: './paths/2014/combined.yml#/magic-items'\n  /api/2014/magic-schools/{index}:\n    $ref: './paths/2014/combined.yml#/magic-schools'\n  /api/2014/monsters:\n    $ref: './paths/2014/combined.yml#/monsters'\n  /api/2014/monsters/{index}:\n    $ref: './paths/2014/combined.yml#/monster'\n  /api/2014/proficiencies/{index}:\n    $ref: './paths/2014/combined.yml#/proficiencies'\n  /api/2014/races/{index}:\n    $ref: './paths/2014/combined.yml#/races'\n  /api/2014/races/{index}/subraces:\n    $ref: './paths/2014/combined.yml#/race-subraces'\n  /api/2014/races/{index}/proficiencies:\n    $ref: './paths/2014/combined.yml#/race-proficiencies'\n  /api/2014/races/{index}/traits:\n    $ref: './paths/2014/combined.yml#/race-traits'\n  /api/2014/rule-sections/{index}:\n    $ref: './paths/2014/combined.yml#/rule-sections'\n  /api/2014/rules/{index}:\n    $ref: './paths/2014/combined.yml#/rules'\n  /api/2014/skills/{index}:\n    $ref: './paths/2014/combined.yml#/skills'\n  /api/2014/spells:\n    $ref: './paths/2014/combined.yml#/spells'\n  /api/2014/spells/{index}:\n    $ref: './paths/2014/combined.yml#/spell'\n  /api/2014/subclasses/{index}:\n    $ref: './paths/2014/combined.yml#/subclasses'\n  /api/2014/subclasses/{index}/features:\n    $ref: './paths/2014/combined.yml#/subclass-features'\n  /api/2014/subclasses/{index}/levels:\n    $ref: './paths/2014/combined.yml#/subclass-levels'\n  /api/2014/subclasses/{index}/levels/{subclass_level}:\n    $ref: './paths/2014/combined.yml#/subclass-level'\n  /api/2014/subclasses/{index}/levels/{subclass_level}/features:\n    $ref: './paths/2014/combined.yml#/subclass-level-features'\n  /api/2014/subraces/{index}:\n    $ref: './paths/2014/combined.yml#/subraces'\n  /api/2014/subraces/{index}/proficiencies:\n    $ref: './paths/2014/combined.yml#/subrace-proficiencies'\n  /api/2014/subraces/{index}/traits:\n    $ref: './paths/2014/combined.yml#/subrace-traits'\n  /api/2014/traits/{index}:\n    $ref: './paths/2014/combined.yml#/traits'\n  /api/2014/weapon-properties/{index}:\n    $ref: './paths/2014/combined.yml#/weapon-properties'\n\ncomponents:\n  parameters:\n    $ref: './parameters/2014/combined.yml'\n\n  schemas:\n    $ref: './schemas/2014/combined.yml'\n\n# Add security definitions\nsecurity:\n  - {} # Empty security requirement means no authentication required\n"
  },
  {
    "path": "src/tests/controllers/api/2014/abilityScoreController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport AbilityScoreController from '@/controllers/api/2014/abilityScoreController'\nimport AbilityScoreModel from '@/models/2014/abilityScore'\nimport { abilityScoreFactory } from '@/tests/factories/2014/abilityScore.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('abilityscore')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(AbilityScoreModel)\n\ndescribe('AbilityScoreController', () => {\n  describe('index', () => {\n    it('returns a list of ability scores', async () => {\n      // Arrange: Seed the database\n      const abilityScoresData = abilityScoreFactory.buildList(3)\n      await AbilityScoreModel.insertMany(abilityScoresData)\n\n      const request = createRequest()\n      const response = createResponse()\n\n      // Act\n      await AbilityScoreController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results.length).toBe(3)\n      // Check if the returned data loosely matches the seeded data (checking name/index)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({\n            index: abilityScoresData[0].index,\n            name: abilityScoresData[0].name\n          }),\n          expect.objectContaining({\n            index: abilityScoresData[1].index,\n            name: abilityScoresData[1].name\n          }),\n          expect.objectContaining({\n            index: abilityScoresData[2].index,\n            name: abilityScoresData[2].name\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    // Skipping the explicit 'find error' mock test for now.\n  })\n\n  describe('show', () => {\n    it('returns a single ability score when found', async () => {\n      // Arrange: Seed the database\n      const abilityScoreData = abilityScoreFactory.build({ index: 'cha', name: 'CHA' })\n      await AbilityScoreModel.insertMany([abilityScoreData])\n\n      const request = createRequest({ params: { index: 'cha' } })\n      const response = createResponse()\n\n      // Act\n      await AbilityScoreController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('cha')\n      expect(responseData.name).toBe('CHA')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next with an error if the ability score is not found', async () => {\n      // Arrange: Database is empty (guaranteed by setupModelCleanup)\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      // Act\n      await AbilityScoreController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200) // Default node-mocks-http status\n      expect(response._getData()).toBe('') // No data written before error\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith() // Expect next() called with no arguments\n    })\n\n    // Skipping the explicit 'findOne error' mock test for similar reasons as above.\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/alignmentController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport AlignmentController from '@/controllers/api/2014/alignmentController'\nimport AlignmentModel from '@/models/2014/alignment'\nimport { alignmentFactory } from '@/tests/factories/2014/alignment.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support' // Assuming support helper location\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('alignment')\n\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(AlignmentModel)\n\ndescribe('AlignmentController', () => {\n  describe('index', () => {\n    it('returns a list of alignments', async () => {\n      const alignmentsData = alignmentFactory.buildList(3)\n      const alignmentDocs = alignmentsData.map((data) => new AlignmentModel(data))\n      await AlignmentModel.insertMany(alignmentDocs)\n\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await AlignmentController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({ index: alignmentsData[0].index, name: alignmentsData[0].name }),\n          expect.objectContaining({ index: alignmentsData[1].index, name: alignmentsData[1].name }),\n          expect.objectContaining({ index: alignmentsData[2].index, name: alignmentsData[2].name })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no alignments exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await AlignmentController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single alignment when found', async () => {\n      const alignmentData = alignmentFactory.build({ index: 'lg', name: 'Lawful Good' })\n      await AlignmentModel.insertMany([alignmentData])\n\n      const request = createRequest({ params: { index: 'lg' } })\n      const response = createResponse()\n\n      await AlignmentController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('lg')\n      expect(responseData.name).toBe('Lawful Good')\n      expect(responseData.desc).toEqual(alignmentData.desc)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the alignment is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await AlignmentController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/backgroundController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport BackgroundController from '@/controllers/api/2014/backgroundController'\nimport BackgroundModel from '@/models/2014/background'\nimport { backgroundFactory } from '@/tests/factories/2014/background.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('background')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(BackgroundModel)\n\ndescribe('BackgroundController', () => {\n  describe('index', () => {\n    it('returns a list of backgrounds', async () => {\n      const backgroundsData = backgroundFactory.buildList(3)\n      const backgroundDocs = backgroundsData.map((data) => new BackgroundModel(data))\n      await BackgroundModel.insertMany(backgroundDocs)\n\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await BackgroundController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({\n            index: backgroundsData[0].index,\n            name: backgroundsData[0].name\n          }),\n          expect.objectContaining({\n            index: backgroundsData[1].index,\n            name: backgroundsData[1].name\n          }),\n          expect.objectContaining({\n            index: backgroundsData[2].index,\n            name: backgroundsData[2].name\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no backgrounds exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await BackgroundController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single background when found', async () => {\n      const backgroundData = backgroundFactory.build({ index: 'acolyte', name: 'Acolyte' })\n      await BackgroundModel.insertMany([backgroundData])\n\n      const request = createRequest({ params: { index: 'acolyte' } })\n      const response = createResponse()\n\n      await BackgroundController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('acolyte')\n      expect(responseData.name).toBe('Acolyte')\n      // Add more specific checks if needed, e.g., for nested properties\n      expect(responseData.feature.name).toEqual(backgroundData.feature.name)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the background is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await BackgroundController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/classController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport * as ClassController from '@/controllers/api/2014/classController'\nimport ClassModel from '@/models/2014/class'\nimport FeatureModel from '@/models/2014/feature'\nimport LevelModel from '@/models/2014/level'\nimport ProficiencyModel from '@/models/2014/proficiency'\nimport SpellModel from '@/models/2014/spell'\nimport SubclassModel from '@/models/2014/subclass'\nimport { classFactory } from '@/tests/factories/2014/class.factory'\nimport { featureFactory } from '@/tests/factories/2014/feature.factory'\nimport { levelFactory } from '@/tests/factories/2014/level.factory'\nimport { proficiencyFactory } from '@/tests/factories/2014/proficiency.factory'\nimport { spellFactory } from '@/tests/factories/2014/spell.factory'\nimport { subclassFactory } from '@/tests/factories/2014/subclass.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\nconst dbUri = generateUniqueDbUri('class')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(ClassModel)\nsetupModelCleanup(LevelModel)\nsetupModelCleanup(SubclassModel)\nsetupModelCleanup(SpellModel)\nsetupModelCleanup(FeatureModel)\nsetupModelCleanup(ProficiencyModel)\n\ndescribe('ClassController', () => {\n  describe('index', () => {\n    it('returns a list of classes', async () => {\n      const classesData = classFactory.buildList(3)\n      await ClassModel.insertMany(classesData)\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await ClassController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({\n            index: classesData[0].index,\n            name: classesData[0].name,\n            url: classesData[0].url\n          }),\n          expect.objectContaining({\n            index: classesData[1].index,\n            name: classesData[1].name,\n            url: classesData[1].url\n          }),\n          expect.objectContaining({\n            index: classesData[2].index,\n            name: classesData[2].name,\n            url: classesData[2].url\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('handles index database errors', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n      const error = new Error('Database find failed')\n      vi.spyOn(ClassModel, 'find').mockImplementationOnce(\n        () =>\n          ({\n            select: vi.fn().mockReturnThis(),\n            sort: vi.fn().mockReturnThis(),\n            exec: vi.fn().mockRejectedValueOnce(error)\n          }) as any\n      )\n      await ClassController.index(request, response, mockNext)\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n\n    it('returns an empty list when no classes exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n      await ClassController.index(request, response, mockNext)\n      expect(response.statusCode).toBe(200)\n      expect(JSON.parse(response._getData())).toEqual({ count: 0, results: [] })\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  // === show ===\n  describe('show', () => {\n    const classIndex = 'barbarian'\n    it('returns a single class when found', async () => {\n      const classData = classFactory.build({ index: classIndex })\n      await ClassModel.insertMany([classData])\n      const request = createRequest({ params: { index: classIndex } })\n      const response = createResponse()\n\n      await ClassController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData).toMatchObject({\n        index: classData.index,\n        name: classData.name,\n        url: classData.url\n      })\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the class is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n      await ClassController.show(request, response, mockNext)\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n\n    it('handles show database errors', async () => {\n      const request = createRequest({ params: { index: classIndex } })\n      const response = createResponse()\n      const error = new Error('Database findOne failed')\n      vi.spyOn(ClassModel, 'findOne').mockRejectedValueOnce(error)\n      await ClassController.show(request, response, mockNext)\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n  })\n\n  // === showLevelsForClass ===\n  describe('showLevelsForClass', () => {\n    const classIndex = 'wizard'\n    const classUrl = `/api/2014/classes/${classIndex}`\n    it('returns levels for a specific class', async () => {\n      const classData = classFactory.build({ index: classIndex, url: classUrl })\n      await ClassModel.insertMany([classData])\n      const levelsData = levelFactory.buildList(5, {\n        class: { index: classIndex, name: classData.name, url: classUrl }\n      })\n      await LevelModel.insertMany(levelsData)\n      await LevelModel.insertMany(levelFactory.buildList(2))\n\n      const request = createRequest({ query: {}, params: { index: classIndex } })\n      const response = createResponse()\n\n      await ClassController.showLevelsForClass(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData).toBeInstanceOf(Array)\n      expect(responseData).toHaveLength(5)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns 404 if class has no levels', async () => {\n      const classData = classFactory.build({ index: classIndex, url: classUrl })\n      await ClassModel.insertMany([classData])\n      const request = createRequest({ query: {}, params: { index: classIndex } })\n      const response = createResponse()\n      await ClassController.showLevelsForClass(request, response, mockNext)\n      expect(response.statusCode).toBe(404)\n      expect(JSON.parse(response._getData())).toEqual({ error: 'Not found' })\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns 404 if class index is invalid', async () => {\n      const request = createRequest({ query: {}, params: { index: 'nonexistent' } })\n      const response = createResponse()\n      await ClassController.showLevelsForClass(request, response, mockNext)\n      expect(response.statusCode).toBe(404)\n      expect(JSON.parse(response._getData())).toEqual({ error: 'Not found' })\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('handles levels database errors', async () => {\n      const classData = classFactory.build({ index: classIndex, url: classUrl })\n      await ClassModel.insertMany([classData])\n      const request = createRequest({ query: {}, params: { index: classIndex } })\n      const response = createResponse()\n      const error = new Error('Level find failed')\n      vi.spyOn(LevelModel, 'find').mockImplementationOnce(\n        () =>\n          ({\n            sort: vi.fn().mockRejectedValueOnce(error)\n          }) as any\n      )\n      await ClassController.showLevelsForClass(request, response, mockNext)\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n  })\n\n  // === showLevelForClass ===\n  describe('showLevelForClass', () => {\n    const classIndex = 'wizard'\n    const targetLevel = 5\n    const levelUrl = `/api/2014/classes/${classIndex}/levels/${targetLevel}`\n\n    it('returns a specific level for a class', async () => {\n      const classData = classFactory.build({ index: classIndex })\n      await ClassModel.insertMany([classData])\n      const levelData = levelFactory.build({\n        level: targetLevel,\n        class: { index: classIndex, name: classData.name, url: `/api/2014/classes/${classIndex}` },\n        url: levelUrl\n      })\n      await LevelModel.insertMany([levelData])\n\n      const request = createRequest({ params: { index: classIndex, level: String(targetLevel) } })\n      const response = createResponse()\n\n      await ClassController.showLevelForClass(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData).toMatchObject({ level: levelData.level, url: levelData.url })\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the specific level is not found', async () => {\n      const request = createRequest({ params: { index: classIndex, level: '10' } })\n      const response = createResponse()\n      await ClassController.showLevelForClass(request, response, mockNext)\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n\n    it('handles level findOne database errors', async () => {\n      const request = createRequest({ params: { index: classIndex, level: String(targetLevel) } })\n      const response = createResponse()\n      const error = new Error('Level findOne failed')\n      vi.spyOn(LevelModel, 'findOne').mockRejectedValueOnce(error)\n      await ClassController.showLevelForClass(request, response, mockNext)\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n\n    it('returns 400 for invalid level parameter (non-numeric)', async () => {\n      const request = createRequest({ params: { index: classIndex, level: 'invalid' } })\n      const response = createResponse()\n      await ClassController.showLevelForClass(request, response, mockNext)\n      expect(response.statusCode).toBe(400)\n      expect(JSON.parse(response._getData()).error).toContain('Invalid path parameters')\n    })\n\n    it('returns 400 for invalid level parameter (out of range)', async () => {\n      const request = createRequest({ params: { index: classIndex, level: '21' } })\n      const response = createResponse()\n      await ClassController.showLevelForClass(request, response, mockNext)\n      expect(response.statusCode).toBe(400)\n      expect(JSON.parse(response._getData()).error).toContain('Invalid path parameters')\n    })\n  })\n\n  // === showSubclassesForClass ===\n  describe('showSubclassesForClass', () => {\n    const classIndex = 'barbarian'\n    const classUrl = `/api/2014/classes/${classIndex}`\n    it('returns subclasses for a specific class', async () => {\n      const classData = classFactory.build({ index: classIndex, url: classUrl })\n      await ClassModel.insertMany([classData])\n      const subclassesData = subclassFactory.buildList(2, {\n        class: { index: classIndex, name: classData.name, url: classUrl }\n      })\n      await SubclassModel.insertMany(subclassesData)\n      await SubclassModel.insertMany(subclassFactory.buildList(1))\n\n      const request = createRequest({ params: { index: classIndex } })\n      const response = createResponse()\n\n      await ClassController.showSubclassesForClass(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData).toHaveProperty('count', 2)\n      expect(responseData).toHaveProperty('results')\n      expect(responseData.results).toHaveLength(2)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns 404 if class index is invalid', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n      await ClassController.showSubclassesForClass(request, response, mockNext)\n      expect(response.statusCode).toBe(404)\n      expect(JSON.parse(response._getData()).error).toEqual('Not found')\n    })\n\n    it('handles subclass find database errors', async () => {\n      const classData = classFactory.build({ index: classIndex, url: classUrl })\n      await ClassModel.insertMany([classData])\n      const request = createRequest({ params: { index: classIndex } })\n      const response = createResponse()\n      const error = new Error('Subclass find failed')\n      vi.spyOn(SubclassModel, 'find').mockImplementationOnce(\n        () =>\n          ({\n            select: vi.fn().mockReturnThis(),\n            sort: vi.fn().mockRejectedValueOnce(error)\n          }) as any\n      )\n      await ClassController.showSubclassesForClass(request, response, mockNext)\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n  })\n\n  // === showSpellsForClass ===\n  describe('showSpellsForClass', () => {\n    const classIndex = 'wizard'\n    const classUrl = `/api/2014/classes/${classIndex}`\n\n    it('returns spells for a specific class', async () => {\n      const classData = classFactory.build({ index: classIndex, url: classUrl })\n      await ClassModel.insertMany([classData])\n      const spellsData = spellFactory.buildList(3, {\n        classes: [{ index: classIndex, name: classData.name, url: classUrl }]\n      })\n      await SpellModel.insertMany(spellsData)\n      await SpellModel.insertMany(spellFactory.buildList(2))\n\n      const request = createRequest({ params: { index: classIndex } })\n      const response = createResponse()\n\n      await ClassController.showSpellsForClass(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns spells filtered by a single level', async () => {\n      const classData = classFactory.build({ index: classIndex, url: classUrl })\n      await ClassModel.insertMany([classData])\n      const spellsLevel1 = spellFactory.buildList(2, {\n        level: 1,\n        classes: [{ index: classIndex, name: classData.name, url: classUrl }]\n      })\n      const spellsLevel2 = spellFactory.buildList(1, {\n        level: 2,\n        classes: [{ index: classIndex, name: classData.name, url: classUrl }]\n      })\n      await SpellModel.insertMany([...spellsLevel1, ...spellsLevel2])\n\n      const request = createRequest({ query: { level: '1' }, params: { index: classIndex } })\n      const response = createResponse()\n\n      await ClassController.showSpellsForClass(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(2)\n      expect(responseData.results).toHaveLength(2)\n      responseData.results.forEach((spell: any) => {\n        expect(spell.level).toBe(1)\n      })\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns spells filtered by multiple levels', async () => {\n      const classData = classFactory.build({ index: classIndex, url: classUrl })\n      await ClassModel.insertMany([classData])\n      const spellsLevel1 = spellFactory.buildList(2, {\n        level: 1,\n        classes: [{ index: classIndex, name: classData.name, url: classUrl }]\n      })\n      const spellsLevel2 = spellFactory.buildList(1, {\n        level: 2,\n        classes: [{ index: classIndex, name: classData.name, url: classUrl }]\n      })\n      const spellsLevel3 = spellFactory.buildList(1, {\n        level: 3,\n        classes: [{ index: classIndex, name: classData.name, url: classUrl }]\n      })\n      await SpellModel.insertMany([...spellsLevel1, ...spellsLevel2, ...spellsLevel3])\n\n      const request = createRequest({\n        query: { level: ['1', '3'] },\n        params: { index: classIndex }\n      })\n      const response = createResponse()\n\n      await ClassController.showSpellsForClass(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3) // 2 level 1 + 1 level 3\n      expect(responseData.results).toHaveLength(3)\n      responseData.results.forEach((spell: any) => {\n        expect([1, 3]).toContain(spell.level)\n      })\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list if no spells match the level filter', async () => {\n      const classData = classFactory.build({ index: classIndex, url: classUrl })\n      await ClassModel.insertMany([classData])\n      const spellsLevel1 = spellFactory.buildList(2, {\n        level: 1,\n        classes: [{ index: classIndex, name: classData.name, url: classUrl }]\n      })\n      await SpellModel.insertMany(spellsLevel1)\n\n      const request = createRequest({ query: { level: '5' }, params: { index: classIndex } })\n      const response = createResponse()\n\n      await ClassController.showSpellsForClass(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toHaveLength(0)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns 404 if class index is invalid, regardless of level query', async () => {\n      const request = createRequest({ query: { level: '1' }, params: { index: 'nonexistent' } })\n      const response = createResponse()\n      await ClassController.showSpellsForClass(request, response, mockNext)\n      expect(response.statusCode).toBe(404)\n      expect(JSON.parse(response._getData())).toEqual({ error: 'Not found' })\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns 400 for invalid level parameter (non-numeric string)', async () => {\n      const classData = classFactory.build({ index: classIndex, url: classUrl })\n      await ClassModel.insertMany([classData])\n      const request = createRequest({ query: { level: 'abc' }, params: { index: classIndex } })\n      const response = createResponse()\n\n      await ClassController.showSpellsForClass(request, response, mockNext)\n\n      expect(response.statusCode).toBe(400)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.error).toBe('Invalid query parameters')\n      expect(responseData.details).toBeInstanceOf(Array)\n      expect(responseData.details[0].path).toEqual(['level'])\n      expect(responseData.details[0].message).toContain('Invalid')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns 400 for invalid level parameter (in an array)', async () => {\n      const classData = classFactory.build({ index: classIndex, url: classUrl })\n      await ClassModel.insertMany([classData])\n      const request = createRequest({\n        query: { level: ['1', 'abc'] },\n        params: { index: classIndex }\n      })\n      const response = createResponse()\n\n      await ClassController.showSpellsForClass(request, response, mockNext)\n\n      expect(response.statusCode).toBe(400)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.error).toBe('Invalid query parameters')\n      expect(responseData.details).toBeInstanceOf(Array)\n      expect(responseData.details[0].path).toEqual(['level', 1])\n      expect(responseData.details[0].message).toContain('Invalid')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('handles database errors gracefully', async () => {\n      const classData = classFactory.build({ index: classIndex, url: classUrl })\n      await ClassModel.insertMany([classData])\n      const request = createRequest({ params: { index: classIndex } })\n      const response = createResponse()\n      const error = new Error('Spell find failed')\n      vi.spyOn(SpellModel, 'find').mockImplementationOnce(\n        () =>\n          ({\n            select: vi.fn().mockReturnThis(),\n            sort: vi.fn().mockRejectedValueOnce(error)\n          }) as any\n      )\n      await ClassController.showSpellsForClass(request, response, mockNext)\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n  })\n\n  // === showFeaturesForClass ===\n  describe('showFeaturesForClass', () => {\n    const classIndex = 'fighter'\n    const classUrl = `/api/2014/classes/${classIndex}`\n    it('returns features for a specific class', async () => {\n      const classData = classFactory.build({ index: classIndex, url: classUrl })\n      await ClassModel.insertMany([classData])\n      const featuresData = featureFactory.buildList(4, {\n        class: { index: classIndex, name: classData.name, url: classUrl }\n      })\n      await FeatureModel.insertMany(featuresData)\n      await FeatureModel.insertMany(featureFactory.buildList(2))\n\n      const request = createRequest({ params: { index: classIndex } })\n      const response = createResponse()\n\n      await ClassController.showFeaturesForClass(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData).toHaveProperty('count', 4)\n      expect(responseData.results).toHaveLength(4)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns 404 if class index is invalid', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n      await ClassController.showFeaturesForClass(request, response, mockNext)\n      expect(response.statusCode).toBe(404)\n      expect(JSON.parse(response._getData())).toEqual({ error: 'Not found' })\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('handles feature find database errors', async () => {\n      const classData = classFactory.build({ index: classIndex, url: classUrl })\n      await ClassModel.insertMany([classData])\n      const request = createRequest({ params: { index: classIndex } })\n      const response = createResponse()\n      const error = new Error('Feature find failed')\n      vi.spyOn(FeatureModel, 'find').mockImplementationOnce(\n        () =>\n          ({\n            select: vi.fn().mockReturnThis(),\n            sort: vi.fn().mockRejectedValueOnce(error)\n          }) as any\n      )\n      await ClassController.showFeaturesForClass(request, response, mockNext)\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n  })\n\n  // === showProficienciesForClass ===\n  describe('showProficienciesForClass', () => {\n    const classIndex = 'rogue'\n    const classUrl = `/api/2014/classes/${classIndex}`\n    it('returns proficiencies for a specific class', async () => {\n      const classData = classFactory.build({ index: classIndex, url: classUrl })\n      await ClassModel.insertMany([classData])\n      const profsData = proficiencyFactory.buildList(5, {\n        classes: [{ index: classIndex, name: classData.name, url: classUrl }]\n      })\n      await ProficiencyModel.insertMany(profsData)\n      await ProficiencyModel.insertMany(proficiencyFactory.buildList(2))\n\n      const request = createRequest({ params: { index: classIndex } })\n      const response = createResponse()\n\n      await ClassController.showProficienciesForClass(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData).toHaveProperty('count', 5)\n      expect(responseData.results).toHaveLength(5)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns 404 if class index is invalid', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n      await ClassController.showProficienciesForClass(request, response, mockNext)\n      expect(response.statusCode).toBe(404)\n      expect(JSON.parse(response._getData())).toEqual({ error: 'Not found' })\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('handles proficiency find database errors', async () => {\n      const classData = classFactory.build({ index: classIndex, url: classUrl })\n      await ClassModel.insertMany([classData])\n      const request = createRequest({ params: { index: classIndex } })\n      const response = createResponse()\n      const error = new Error('Proficiency find failed')\n      vi.spyOn(ProficiencyModel, 'find').mockImplementationOnce(\n        () =>\n          ({\n            select: vi.fn().mockReturnThis(),\n            sort: vi.fn().mockRejectedValueOnce(error)\n          }) as any\n      )\n      await ClassController.showProficienciesForClass(request, response, mockNext)\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/conditionController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport ConditionController from '@/controllers/api/2014/conditionController'\nimport ConditionModel from '@/models/2014/condition'\nimport { conditionFactory } from '@/tests/factories/2014/condition.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('condition')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(ConditionModel)\n\ndescribe('ConditionController', () => {\n  describe('index', () => {\n    it('returns a list of conditions', async () => {\n      const conditionsData = conditionFactory.buildList(3)\n      const conditionDocs = conditionsData.map((data) => new ConditionModel(data))\n      await ConditionModel.insertMany(conditionDocs)\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await ConditionController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({ index: conditionsData[0].index, name: conditionsData[0].name }),\n          expect.objectContaining({ index: conditionsData[1].index, name: conditionsData[1].name }),\n          expect.objectContaining({ index: conditionsData[2].index, name: conditionsData[2].name })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no conditions exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await ConditionController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single condition when found', async () => {\n      const conditionData = conditionFactory.build({ index: 'blinded', name: 'Blinded' })\n      await ConditionModel.insertMany([conditionData])\n      const request = createRequest({ params: { index: 'blinded' } })\n      const response = createResponse()\n\n      await ConditionController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('blinded')\n      expect(responseData.name).toBe('Blinded')\n      expect(responseData.desc).toEqual(conditionData.desc)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the condition is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await ConditionController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/damageTypeController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport DamageTypeController from '@/controllers/api/2014/damageTypeController'\nimport DamageTypeModel from '@/models/2014/damageType'\nimport { damageTypeFactory } from '@/tests/factories/2014/damageType.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('damageType')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(DamageTypeModel)\n\ndescribe('DamageTypeController', () => {\n  describe('index', () => {\n    it('returns a list of damage types', async () => {\n      const damageTypesData = damageTypeFactory.buildList(3)\n      const damageTypeDocs = damageTypesData.map((data) => new DamageTypeModel(data))\n      await DamageTypeModel.insertMany(damageTypeDocs)\n\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await DamageTypeController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({\n            index: damageTypesData[0].index,\n            name: damageTypesData[0].name\n          }),\n          expect.objectContaining({\n            index: damageTypesData[1].index,\n            name: damageTypesData[1].name\n          }),\n          expect.objectContaining({\n            index: damageTypesData[2].index,\n            name: damageTypesData[2].name\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no damage types exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await DamageTypeController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single damage type when found', async () => {\n      const damageTypeData = damageTypeFactory.build({ index: 'acid', name: 'Acid' })\n      await DamageTypeModel.insertMany([damageTypeData])\n\n      const request = createRequest({ params: { index: 'acid' } })\n      const response = createResponse()\n\n      await DamageTypeController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('acid')\n      expect(responseData.name).toBe('Acid')\n      expect(responseData.desc).toEqual(damageTypeData.desc)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the damage type is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await DamageTypeController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/equipmentCategoryController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport EquipmentCategoryController from '@/controllers/api/2014/equipmentCategoryController'\nimport EquipmentCategoryModel from '@/models/2014/equipmentCategory'\nimport { equipmentCategoryFactory } from '@/tests/factories/2014/equipmentCategory.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('equipmentcategory')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(EquipmentCategoryModel)\n\ndescribe('EquipmentCategoryController', () => {\n  describe('index', () => {\n    it('returns a list of equipment categories', async () => {\n      const categoriesData = equipmentCategoryFactory.buildList(3)\n      const categoryDocs = categoriesData.map((data) => new EquipmentCategoryModel(data))\n      await EquipmentCategoryModel.insertMany(categoryDocs)\n\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await EquipmentCategoryController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({ index: categoriesData[0].index, name: categoriesData[0].name }),\n          expect.objectContaining({ index: categoriesData[1].index, name: categoriesData[1].name }),\n          expect.objectContaining({ index: categoriesData[2].index, name: categoriesData[2].name })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no equipment categories exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await EquipmentCategoryController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single equipment category when found', async () => {\n      const categoryData = equipmentCategoryFactory.build({ index: 'armor', name: 'Armor' })\n      await EquipmentCategoryModel.insertMany([categoryData])\n\n      const request = createRequest({ params: { index: 'armor' } })\n      const response = createResponse()\n\n      await EquipmentCategoryController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('armor')\n      expect(responseData.name).toBe('Armor')\n      expect(responseData.equipment).toHaveLength(categoryData.equipment?.length ?? 0)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the equipment category is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await EquipmentCategoryController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/equipmentController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport EquipmentController from '@/controllers/api/2014/equipmentController'\nimport EquipmentModel from '@/models/2014/equipment'\nimport { equipmentFactory } from '@/tests/factories/2014/equipment.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('equipment')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(EquipmentModel)\n\ndescribe('EquipmentController', () => {\n  describe('index', () => {\n    it('returns a list of equipment', async () => {\n      const equipmentData = equipmentFactory.buildList(3)\n      const equipmentDocs = equipmentData.map((data) => new EquipmentModel(data))\n      await EquipmentModel.insertMany(equipmentDocs)\n\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await EquipmentController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({ index: equipmentData[0].index, name: equipmentData[0].name }),\n          expect.objectContaining({ index: equipmentData[1].index, name: equipmentData[1].name }),\n          expect.objectContaining({ index: equipmentData[2].index, name: equipmentData[2].name })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no equipment exists', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await EquipmentController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single piece of equipment when found', async () => {\n      const equipmentData = equipmentFactory.build({ index: 'club', name: 'Club' })\n      await EquipmentModel.insertMany([equipmentData])\n\n      const request = createRequest({ params: { index: 'club' } })\n      const response = createResponse()\n\n      await EquipmentController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('club')\n      expect(responseData.name).toBe('Club')\n      expect(responseData.desc).toEqual(equipmentData.desc)\n      expect(responseData.cost.quantity).toEqual(equipmentData.cost.quantity)\n      expect(responseData.equipment_category.index).toEqual(equipmentData.equipment_category.index)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the equipment is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await EquipmentController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/featController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport FeatController from '@/controllers/api/2014/featController'\nimport FeatModel from '@/models/2014/feat' // Use Model suffix\nimport { featFactory } from '@/tests/factories/2014/feat.factory' // Import factory\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('feat')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(FeatModel)\n\ndescribe('FeatController', () => {\n  describe('index', () => {\n    it('returns a list of feats', async () => {\n      // Arrange\n      const featsData = featFactory.buildList(3)\n      await FeatModel.insertMany(featsData)\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      // Act\n      await FeatController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          // Index action returns index, name, url\n          expect.objectContaining({\n            index: featsData[0].index,\n            name: featsData[0].name,\n            url: featsData[0].url\n          }),\n          expect.objectContaining({\n            index: featsData[1].index,\n            name: featsData[1].name,\n            url: featsData[1].url\n          }),\n          expect.objectContaining({\n            index: featsData[2].index,\n            name: featsData[2].name,\n            url: featsData[2].url\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('handles database errors during find', async () => {\n      // Arrange\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n      const error = new Error('Database find failed')\n      vi.spyOn(FeatModel, 'find').mockImplementationOnce(() => {\n        const query = {\n          select: vi.fn().mockReturnThis(),\n          sort: vi.fn().mockReturnThis(),\n          exec: vi.fn().mockRejectedValueOnce(error)\n        } as any\n        return query\n      })\n\n      // Act\n      await FeatController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200) // Controller passes error to next()\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n\n    it('returns an empty list when no feats exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n      await FeatController.index(request, response, mockNext)\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single feat when found', async () => {\n      // Arrange\n      const featData = featFactory.build({ index: 'tough', name: 'Tough' })\n      await FeatModel.insertMany([featData])\n      const request = createRequest({ params: { index: 'tough' } })\n      const response = createResponse()\n\n      // Act\n      await FeatController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      // Check specific fields returned by show\n      expect(responseData).toMatchObject({\n        index: featData.index,\n        name: featData.name,\n        prerequisites: expect.any(Array), // Check structure if needed\n        desc: featData.desc,\n        url: featData.url\n      })\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the feat is not found', async () => {\n      // Arrange\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      // Act\n      await FeatController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200) // Passes to next()\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n\n    it('handles database errors during findOne', async () => {\n      // Arrange\n      const request = createRequest({ params: { index: 'tough' } })\n      const response = createResponse()\n      const error = new Error('Database findOne failed')\n      vi.spyOn(FeatModel, 'findOne').mockRejectedValueOnce(error)\n\n      // Act\n      await FeatController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200) // Passes to next()\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/featureController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport FeatureController from '@/controllers/api/2014/featureController'\nimport FeatureModel from '@/models/2014/feature'\nimport { featureFactory } from '@/tests/factories/2014/feature.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('feature')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(FeatureModel)\n\ndescribe('FeatureController', () => {\n  describe('index', () => {\n    it('returns a list of features', async () => {\n      const featuresData = featureFactory.buildList(3)\n      const featureDocs = featuresData.map((data) => new FeatureModel(data))\n      await FeatureModel.insertMany(featureDocs)\n\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await FeatureController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({ index: featuresData[0].index, name: featuresData[0].name }),\n          expect.objectContaining({ index: featuresData[1].index, name: featuresData[1].name }),\n          expect.objectContaining({ index: featuresData[2].index, name: featuresData[2].name })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no features exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await FeatureController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single feature when found', async () => {\n      const featureData = featureFactory.build({ index: 'action-surge', name: 'Action Surge' })\n      await FeatureModel.insertMany([featureData])\n\n      const request = createRequest({ params: { index: 'action-surge' } })\n      const response = createResponse()\n\n      await FeatureController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('action-surge')\n      expect(responseData.name).toBe('Action Surge')\n      expect(responseData.desc).toEqual(featureData.desc)\n      expect(responseData.level).toEqual(featureData.level)\n      expect(responseData.class.index).toEqual(featureData.class.index)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the feature is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await FeatureController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/languageController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport LanguageController from '@/controllers/api/2014/languageController'\nimport LanguageModel from '@/models/2014/language'\nimport { languageFactory } from '@/tests/factories/2014/language.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('language')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(LanguageModel)\n\ndescribe('LanguageController', () => {\n  describe('index', () => {\n    it('returns a list of languages', async () => {\n      const languagesData = languageFactory.buildList(3)\n      const languageDocs = languagesData.map((data) => new LanguageModel(data))\n      await LanguageModel.insertMany(languageDocs)\n\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await LanguageController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({ index: languagesData[0].index, name: languagesData[0].name }),\n          expect.objectContaining({ index: languagesData[1].index, name: languagesData[1].name }),\n          expect.objectContaining({ index: languagesData[2].index, name: languagesData[2].name })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no languages exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await LanguageController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single language when found', async () => {\n      const languageData = languageFactory.build({ index: 'common', name: 'Common' })\n      await LanguageModel.insertMany([languageData])\n\n      const request = createRequest({ params: { index: 'common' } })\n      const response = createResponse()\n\n      await LanguageController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('common')\n      expect(responseData.name).toBe('Common')\n      expect(responseData.script).toEqual(languageData.script)\n      expect(responseData.type).toEqual(languageData.type)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the language is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await LanguageController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/magicItemController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport * as MagicItemController from '@/controllers/api/2014/magicItemController'\nimport MagicItemModel from '@/models/2014/magicItem'\nimport { magicItemFactory } from '@/tests/factories/2014/magicItem.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support' // Assuming mockNext is here based on spell test\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('magicitem')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(MagicItemModel)\n\ndescribe('MagicItemController', () => {\n  describe('index', () => {\n    it('returns a list of magic items', async () => {\n      // Arrange\n      const magicItemsData = magicItemFactory.buildList(3)\n      // Mongoose documents need _id, factory generates data matching the schema without it\n      // Insert directly, letting Mongoose handle _id\n      await MagicItemModel.insertMany(magicItemsData)\n\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      // Act\n      await MagicItemController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      // Check for essential properties returned by the index endpoint\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({\n            index: magicItemsData[0].index,\n            name: magicItemsData[0].name,\n            url: magicItemsData[0].url\n          }),\n          expect.objectContaining({\n            index: magicItemsData[1].index,\n            name: magicItemsData[1].name,\n            url: magicItemsData[1].url\n          }),\n          expect.objectContaining({\n            index: magicItemsData[2].index,\n            name: magicItemsData[2].name,\n            url: magicItemsData[2].url\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('handles database errors during find', async () => {\n      // Arrange\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n      const error = new Error('Database find failed')\n      // Mock the find method to throw an error\n      vi.spyOn(MagicItemModel, 'find').mockImplementationOnce(() => {\n        const query = {\n          select: vi.fn().mockReturnThis(),\n          sort: vi.fn().mockRejectedValueOnce(error)\n          // Add other methods chained in the controller if necessary\n        } as any\n        return query\n      })\n\n      // Act\n      await MagicItemController.index(request, response, mockNext)\n\n      // Assert\n      // Status code might not be set if error is passed to next()\n      expect(response.statusCode).toBe(200) // Or check if response was sent\n      expect(response._getData()).toBe('') // No data sent on error passed to next()\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n\n    it('returns an empty list when no magic items exist', async () => {\n      // Arrange\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      // Act\n      await MagicItemController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    // TODO: Add tests for query parameters (e.g., name) if applicable\n  })\n\n  describe('show', () => {\n    it('returns a single magic item when found', async () => {\n      // Arrange\n      const itemData = magicItemFactory.build({\n        index: 'cloak-of-protection',\n        name: 'Cloak of Protection'\n      })\n      await MagicItemModel.insertMany([itemData]) // Insert as array\n\n      const request = createRequest({ params: { index: 'cloak-of-protection' } })\n      const response = createResponse()\n\n      // Act\n      await MagicItemController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      // Check against the original data, excluding potential virtuals or _id\n      expect(responseData).toMatchObject({\n        index: itemData.index,\n        name: itemData.name,\n        desc: itemData.desc,\n        url: itemData.url\n        // Add other fields as necessary\n      })\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the magic item is not found', async () => {\n      // Arrange\n      const request = createRequest({ params: { index: 'nonexistent-item' } })\n      const response = createResponse()\n\n      // Act\n      await MagicItemController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200) // Controller doesn't set 404, passes to next()\n      expect(response._getData()).toBe('') // No data sent\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith() // Called with no arguments for default 404 handling\n    })\n\n    it('handles database errors during findOne', async () => {\n      // Arrange\n      const request = createRequest({ params: { index: 'any-index' } })\n      const response = createResponse()\n      const error = new Error('Database findOne failed')\n      // Mock findOne to throw an error\n      vi.spyOn(MagicItemModel, 'findOne').mockRejectedValueOnce(error)\n\n      // Act\n      await MagicItemController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/magicSchoolController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport MagicSchoolController from '@/controllers/api/2014/magicSchoolController'\nimport MagicSchoolModel from '@/models/2014/magicSchool' // Use Model suffix\nimport { magicSchoolFactory } from '@/tests/factories/2014/magicSchool.factory' // Updated path\nimport { mockNext as defaultMockNext } from '@/tests/support' // Assuming support helper location\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('magicschool')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(MagicSchoolModel)\n\ndescribe('MagicSchoolController', () => {\n  describe('index', () => {\n    it('returns a list of magic schools', async () => {\n      // Arrange\n      const schoolsData = magicSchoolFactory.buildList(3)\n      await MagicSchoolModel.insertMany(schoolsData)\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      // Act\n      await MagicSchoolController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({ index: schoolsData[0].index, name: schoolsData[0].name }),\n          expect.objectContaining({ index: schoolsData[1].index, name: schoolsData[1].name }),\n          expect.objectContaining({ index: schoolsData[2].index, name: schoolsData[2].name })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no magic schools exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await MagicSchoolController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    // Add error handling test if necessary\n  })\n\n  describe('show', () => {\n    it('returns a single magic school when found', async () => {\n      // Arrange\n      const schoolData = magicSchoolFactory.build({ index: 'evocation', name: 'Evocation' })\n      await MagicSchoolModel.insertMany([schoolData])\n      const request = createRequest({ params: { index: 'evocation' } })\n      const response = createResponse()\n\n      // Act\n      await MagicSchoolController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('evocation')\n      expect(responseData.name).toBe('Evocation')\n      expect(responseData.desc).toEqual(schoolData.desc)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the magic school is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await MagicSchoolController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200) // Controller passes to next()\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith() // Default 404 handling\n    })\n\n    // Add error handling test if necessary\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/monsterController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport * as MonsterController from '@/controllers/api/2014/monsterController'\nimport MonsterModel from '@/models/2014/monster'\nimport { monsterFactory } from '@/tests/factories/2014/monster.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\n// DB Helper Imports\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\n// Remove redis mock - Integration tests will hit the real DB\n// vi.mock('@/util', ...)\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Setup DB isolation\nconst dbUri = generateUniqueDbUri('monster')\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(MonsterModel)\n\n// Removed createMockQuery helper\n\ndescribe('MonsterController', () => {\n  describe('index', () => {\n    it('returns a list of monsters with default query', async () => {\n      // Arrange: Seed the database\n      const monstersData = monsterFactory.buildList(3)\n      await MonsterModel.insertMany(monstersData)\n\n      const request = createRequest({ query: {}, originalUrl: '/api/monsters' })\n      const response = createResponse()\n\n      // Act\n      await MonsterController.index(request, response, mockNext)\n\n      // Assert: Check response based on seeded data\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({ index: monstersData[0].index, name: monstersData[0].name }),\n          expect.objectContaining({ index: monstersData[1].index, name: monstersData[1].name }),\n          expect.objectContaining({ index: monstersData[2].index, name: monstersData[2].name })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    // Keep challenge_rating tests, but they now hit the DB\n    describe('with challenge_rating query', () => {\n      const crTestCases = [\n        { input: '5', expectedCount: 1, seedCRs: [5, 1, 2] },\n        { input: '1,10,0.25', expectedCount: 2, seedCRs: [1, 10, 5] },\n        { input: ['2', '4'], expectedCount: 2, seedCRs: [2, 4, 5] },\n        { input: 'abc,3,def,0.5', expectedCount: 2, seedCRs: [3, 0.5, 1] },\n        { input: ['1', 'xyz', '5'], expectedCount: 2, seedCRs: [1, 5, 10] },\n        { input: 'invalid, nope', expectedCount: 3, seedCRs: [1, 2, 3] }, // Expect all when filter is invalid\n        { input: '', expectedCount: 3, seedCRs: [1, 2, 3] } // Expect all when filter is empty\n      ]\n\n      it.each(crTestCases)(\n        'handles challenge rating: $input',\n        async ({ input, expectedCount, seedCRs }) => {\n          // Arrange: Seed specific CRs\n          const monstersToSeed = seedCRs.map((cr) => monsterFactory.build({ challenge_rating: cr }))\n          await MonsterModel.insertMany(monstersToSeed)\n\n          const request = createRequest({\n            query: { challenge_rating: input },\n            originalUrl: '/api/monsters'\n          })\n          const response = createResponse()\n\n          // Act\n          await MonsterController.index(request, response, mockNext)\n\n          // Assert\n          expect(response.statusCode).toBe(200)\n          const responseData = JSON.parse(response._getData())\n          expect(responseData.count).toBe(expectedCount)\n          expect(responseData.results).toHaveLength(expectedCount)\n          // Optional: Add more specific checks on returned indices if needed\n          expect(mockNext).not.toHaveBeenCalled()\n        }\n      )\n    })\n\n    // No need for explicit DB error mocking now, handled by helpers/real errors\n    // describe('when something goes wrong', ...)\n\n    // Redis tests are removed as we aren't mocking redis here anymore\n    // describe('when data is in Redis cache', ...)\n  })\n\n  describe('show', () => {\n    it('returns a monster object', async () => {\n      // Arrange\n      const monsterData = monsterFactory.build({ index: 'goblin' })\n      await MonsterModel.insertMany([monsterData])\n\n      const request = createRequest({ params: { index: 'goblin' } })\n      const response = createResponse()\n\n      // Act\n      await MonsterController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('goblin')\n      // Add more detailed checks as needed\n      expect(responseData).toHaveProperty('challenge_rating', monsterData.challenge_rating)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the record does not exist', async () => {\n      // Arrange\n      const request = createRequest({ params: { index: 'non-existent' } })\n      const response = createResponse()\n\n      // Act\n      await MonsterController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200) // Controller passes to next\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith() // Default 404 handling\n    })\n\n    // No need for explicit DB error mocking\n    // describe('when something goes wrong', ...)\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/proficiencyController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport ProficiencyController from '@/controllers/api/2014/proficiencyController'\nimport ProficiencyModel from '@/models/2014/proficiency'\nimport { proficiencyFactory } from '@/tests/factories/2014/proficiency.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('proficiency')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(ProficiencyModel)\n\ndescribe('ProficiencyController', () => {\n  describe('index', () => {\n    it('returns a list of proficiencies', async () => {\n      // Arrange\n      const proficienciesData = proficiencyFactory.buildList(3)\n      await ProficiencyModel.insertMany(proficienciesData)\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      // Act\n      await ProficiencyController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({\n            index: proficienciesData[0].index,\n            name: proficienciesData[0].name\n          }),\n          expect.objectContaining({\n            index: proficienciesData[1].index,\n            name: proficienciesData[1].name\n          }),\n          expect.objectContaining({\n            index: proficienciesData[2].index,\n            name: proficienciesData[2].name\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no proficiencies exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await ProficiencyController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single proficiency when found', async () => {\n      // Arrange\n      const proficiencyData = proficiencyFactory.build({\n        index: 'saving-throw-str',\n        name: 'Saving Throw: STR'\n      })\n      await ProficiencyModel.insertMany([proficiencyData])\n      const request = createRequest({ params: { index: 'saving-throw-str' } })\n      const response = createResponse()\n\n      // Act\n      await ProficiencyController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('saving-throw-str')\n      expect(responseData.name).toBe('Saving Throw: STR')\n      // Add more specific assertions based on the model structure\n      expect(responseData.type).toEqual(proficiencyData.type)\n      // Check optional fields only if they exist on the source data\n      if (proficiencyData.classes) {\n        expect(responseData.classes).toEqual(\n          expect.arrayContaining(\n            proficiencyData.classes.map((c) => expect.objectContaining({ index: c.index }))\n          )\n        )\n      } else {\n        expect(responseData.classes).toEqual([]) // Or appropriate default\n      }\n      if (proficiencyData.races) {\n        expect(responseData.races).toEqual(\n          expect.arrayContaining(\n            proficiencyData.races.map((r) => expect.objectContaining({ index: r.index }))\n          )\n        )\n      } else {\n        expect(responseData.races).toEqual([]) // Or appropriate default\n      }\n      expect(responseData.reference.index).toEqual(proficiencyData.reference.index)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the proficiency is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await ProficiencyController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200) // Passes to next()\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith() // Default 404 handling\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/raceController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport * as RaceController from '@/controllers/api/2014/raceController'\nimport ProficiencyModel from '@/models/2014/proficiency'\nimport RaceModel from '@/models/2014/race'\nimport SubraceModel from '@/models/2014/subrace'\nimport TraitModel from '@/models/2014/trait'\nimport { proficiencyFactory } from '@/tests/factories/2014/proficiency.factory'\nimport { raceFactory } from '@/tests/factories/2014/race.factory'\nimport { subraceFactory } from '@/tests/factories/2014/subrace.factory'\nimport { traitFactory } from '@/tests/factories/2014/trait.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Setup DB isolation\nconst dbUri = generateUniqueDbUri('race')\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\n// Cleanup all relevant models\nsetupModelCleanup(RaceModel)\nsetupModelCleanup(SubraceModel)\nsetupModelCleanup(TraitModel)\nsetupModelCleanup(ProficiencyModel)\n\ndescribe('RaceController', () => {\n  describe('index', () => {\n    it('returns a list of races', async () => {\n      // Arrange: Seed DB\n      const racesData = raceFactory.buildList(3)\n      await RaceModel.insertMany(racesData)\n\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      // Act\n      await RaceController.index(request, response, mockNext)\n\n      // Assert: Check response based on seeded data\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({ index: racesData[0].index, name: racesData[0].name }),\n          expect.objectContaining({ index: racesData[1].index, name: racesData[1].name }),\n          expect.objectContaining({ index: racesData[2].index, name: racesData[2].name })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    // describe('when something goes wrong', ...)\n  })\n\n  describe('show', () => {\n    it('returns a single race', async () => {\n      // Arrange\n      const raceData = raceFactory.build({ index: 'human' })\n      await RaceModel.insertMany([raceData])\n      const request = createRequest({ params: { index: 'human' } })\n      const response = createResponse()\n\n      // Act\n      await RaceController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toEqual(raceData.index)\n      // Add more specific checks\n      expect(responseData.name).toEqual(raceData.name)\n      expect(responseData.speed).toEqual(raceData.speed)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the record does not exist', async () => {\n      // Arrange\n      const request = createRequest({ params: { index: 'non-existent' } })\n      const response = createResponse()\n      // Act\n      await RaceController.show(request, response, mockNext)\n      // Assert\n      expect(mockNext).toHaveBeenCalledWith()\n      expect(response._getData()).toBe('')\n    })\n\n    // describe('when something goes wrong', ...)\n  })\n\n  describe('showSubracesForRace', () => {\n    const raceIndex = 'dwarf'\n    const raceUrl = `/api/2014/races/${raceIndex}`\n\n    it('returns a list of subraces for the race', async () => {\n      // Arrange\n      const raceRef = { index: raceIndex, name: 'Dwarf', url: raceUrl }\n      const subracesData = subraceFactory.buildList(2, { race: raceRef })\n      await SubraceModel.insertMany(subracesData)\n      // Seed an unrelated subrace\n      await SubraceModel.insertMany(subraceFactory.buildList(1))\n\n      const request = createRequest({ params: { index: raceIndex } })\n      const response = createResponse()\n\n      // Act\n      await RaceController.showSubracesForRace(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(2)\n      expect(responseData.results).toHaveLength(2)\n      expect(responseData.results.map((r: any) => r.index)).toEqual(\n        expect.arrayContaining([subracesData[0].index, subracesData[1].index])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    // describe('when something goes wrong', ...)\n  })\n\n  describe('showTraitsForRace', () => {\n    const raceIndex = 'elf'\n    const raceUrl = `/api/2014/races/${raceIndex}`\n\n    it('returns a list of traits for the race', async () => {\n      // Arrange\n      const raceRef = { index: raceIndex, name: 'Elf', url: raceUrl }\n      const traitsData = traitFactory.buildList(3, { races: [raceRef] })\n      await TraitModel.insertMany(traitsData)\n      // Seed unrelated traits\n      await TraitModel.insertMany(traitFactory.buildList(2))\n\n      const request = createRequest({ params: { index: raceIndex } })\n      const response = createResponse()\n\n      // Act\n      await RaceController.showTraitsForRace(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results.map((t: any) => t.index)).toEqual(\n        expect.arrayContaining([traitsData[0].index, traitsData[1].index, traitsData[2].index])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    // describe('when something goes wrong', ...)\n  })\n\n  describe('showProficienciesForRace', () => {\n    const raceIndex = 'halfling'\n    const raceUrl = `/api/2014/races/${raceIndex}`\n\n    it('returns a list of proficiencies for the race', async () => {\n      // Arrange\n      const raceRef = { index: raceIndex, name: 'Halfling', url: raceUrl }\n      const proficienciesData = proficiencyFactory.buildList(4, { races: [raceRef] })\n      await ProficiencyModel.insertMany(proficienciesData)\n      // Seed unrelated proficiencies\n      await ProficiencyModel.insertMany(proficiencyFactory.buildList(2))\n\n      const request = createRequest({ params: { index: raceIndex } })\n      const response = createResponse()\n\n      // Act\n      await RaceController.showProficienciesForRace(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(4)\n      expect(responseData.results).toHaveLength(4)\n      expect(responseData.results.map((p: any) => p.index)).toEqual(\n        expect.arrayContaining([\n          proficienciesData[0].index,\n          proficienciesData[1].index,\n          proficienciesData[2].index,\n          proficienciesData[3].index\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    // describe('when something goes wrong', ...)\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/ruleSectionController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport * as RuleSectionController from '@/controllers/api/2014/ruleSectionController'\nimport RuleSectionModel from '@/models/2014/ruleSection' // Use Model suffix\nimport { ruleSectionFactory } from '@/tests/factories/2014/ruleSection.factory' // Updated path\nimport { mockNext as defaultMockNext } from '@/tests/support' // Assuming support helper location\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('rulesection')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(RuleSectionModel)\n\ndescribe('RuleSectionController', () => {\n  describe('index', () => {\n    it('returns a list of rule sections', async () => {\n      // Arrange\n      const ruleSectionsData = ruleSectionFactory.buildList(3)\n      await RuleSectionModel.insertMany(ruleSectionsData)\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      // Act\n      await RuleSectionController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({\n            index: ruleSectionsData[0].index,\n            name: ruleSectionsData[0].name\n          }),\n          expect.objectContaining({\n            index: ruleSectionsData[1].index,\n            name: ruleSectionsData[1].name\n          }),\n          expect.objectContaining({\n            index: ruleSectionsData[2].index,\n            name: ruleSectionsData[2].name\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no rule sections exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await RuleSectionController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single rule section when found', async () => {\n      // Arrange\n      const ruleSectionData = ruleSectionFactory.build({\n        index: 'adventuring',\n        name: 'Adventuring'\n      })\n      await RuleSectionModel.insertMany([ruleSectionData])\n      const request = createRequest({ params: { index: 'adventuring' } })\n      const response = createResponse()\n\n      // Act\n      await RuleSectionController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('adventuring')\n      expect(responseData.name).toBe('Adventuring')\n      expect(responseData.desc).toEqual(ruleSectionData.desc)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the rule section is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await RuleSectionController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200) // Passes to next()\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith() // Default 404 handling\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/rulesController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\n// Import specific functions from the correct controller file\nimport * as RuleController from '@/controllers/api/2014/ruleController'\nimport RuleModel from '@/models/2014/rule' // Use Model suffix\nimport { ruleFactory } from '@/tests/factories/2014/rule.factory' // Updated path\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('rule')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(RuleModel)\n\ndescribe('RuleController', () => {\n  // Updated describe block name\n  describe('index', () => {\n    it('returns a list of rules', async () => {\n      // Arrange\n      const rulesData = ruleFactory.buildList(3)\n      const ruleDocs = rulesData.map((data) => new RuleModel(data))\n      await RuleModel.insertMany(ruleDocs)\n\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      // Act\n      await RuleController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({ index: rulesData[0].index, name: rulesData[0].name }),\n          expect.objectContaining({ index: rulesData[1].index, name: rulesData[1].name }),\n          expect.objectContaining({ index: rulesData[2].index, name: rulesData[2].name })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no rules exist', async () => {\n      // Arrange\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      // Act\n      await RuleController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single rule when found', async () => {\n      // Arrange\n      const ruleData = ruleFactory.build({ index: 'combat', name: 'Combat' })\n      await RuleModel.insertMany([ruleData]) // Use insertMany workaround\n\n      const request = createRequest({ params: { index: 'combat' } })\n      const response = createResponse()\n\n      // Act\n      await RuleController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('combat')\n      expect(responseData.name).toBe('Combat')\n      expect(responseData.desc).toEqual(ruleData.desc)\n      expect(responseData.subsections).toHaveLength(ruleData.subsections.length)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the rule is not found', async () => {\n      // Arrange\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      // Act\n      await RuleController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/skillController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport SkillController from '@/controllers/api/2014/skillController'\nimport SkillModel from '@/models/2014/skill' // Use Model suffix\nimport { skillFactory } from '@/tests/factories/2014/skill.factory' // Updated path\nimport { mockNext as defaultMockNext } from '@/tests/support' // Assuming support helper location\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('skill')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(SkillModel)\n\ndescribe('SkillController', () => {\n  describe('index', () => {\n    it('returns a list of skills', async () => {\n      // Arrange\n      const skillsData = skillFactory.buildList(3)\n      await SkillModel.insertMany(skillsData)\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      // Act\n      await SkillController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({ index: skillsData[0].index, name: skillsData[0].name }),\n          expect.objectContaining({ index: skillsData[1].index, name: skillsData[1].name }),\n          expect.objectContaining({ index: skillsData[2].index, name: skillsData[2].name })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no skills exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await SkillController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single skill when found', async () => {\n      // Arrange\n      const skillData = skillFactory.build({ index: 'athletics', name: 'Athletics' })\n      await SkillModel.insertMany([skillData])\n      const request = createRequest({ params: { index: 'athletics' } })\n      const response = createResponse()\n\n      // Act\n      await SkillController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('athletics')\n      expect(responseData.name).toBe('Athletics')\n      expect(responseData.desc).toEqual(skillData.desc)\n      // Check nested object\n      expect(responseData.ability_score.index).toEqual(skillData.ability_score.index)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the skill is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await SkillController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200) // Passes to next()\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith() // Default 404 handling\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/spellController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport * as SpellController from '@/controllers/api/2014/spellController'\nimport SpellModel from '@/models/2014/spell'\nimport { spellFactory } from '@/tests/factories/2014/spell.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\n// Import the DB helper functions\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('spell')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(SpellModel)\n\ndescribe('SpellController', () => {\n  describe('index', () => {\n    it('returns a list of spells', async () => {\n      // Arrange\n      const spellsData = spellFactory.buildList(3)\n      // Use insertMany directly\n      await SpellModel.insertMany(spellsData)\n\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      // Act\n      await SpellController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({ index: spellsData[0].index, name: spellsData[0].name }),\n          expect.objectContaining({ index: spellsData[1].index, name: spellsData[1].name }),\n          expect.objectContaining({ index: spellsData[2].index, name: spellsData[2].name })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    // Filters test remains the same\n    it('filters spells by level', async () => {\n      const spellsData = [\n        spellFactory.build({ level: 1, name: 'Spell A' }),\n        spellFactory.build({ level: 2, name: 'Spell B' }),\n        spellFactory.build({ level: 1, name: 'Spell C' })\n      ]\n      await SpellModel.insertMany(spellsData)\n\n      const request = createRequest({ query: { level: '1' } })\n      const response = createResponse()\n\n      await SpellController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(2)\n      expect(responseData.results).toHaveLength(2)\n      expect(responseData.results.map((r: any) => r.index)).toEqual(\n        expect.arrayContaining([spellsData[0].index, spellsData[2].index])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no spells exist', async () => {\n      // Arrange\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      // Act\n      await SpellController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single spell when found', async () => {\n      // Arrange\n      const spellData = spellFactory.build({ index: 'fireball', name: 'Fireball' })\n      await SpellModel.insertMany([spellData])\n\n      const request = createRequest({ params: { index: 'fireball' } })\n      const response = createResponse()\n\n      // Act\n      await SpellController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('fireball')\n      expect(responseData.name).toBe('Fireball')\n      expect(responseData.desc).toEqual(spellData.desc)\n      expect(responseData.level).toEqual(spellData.level)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the spell is not found', async () => {\n      // Arrange\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      // Act\n      await SpellController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/subclassController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport * as SubclassController from '@/controllers/api/2014/subclassController'\nimport FeatureModel from '@/models/2014/feature'\nimport LevelModel from '@/models/2014/level'\nimport SubclassModel from '@/models/2014/subclass'\nimport { apiReferenceFactory } from '@/tests/factories/2014/common.factory'\nimport { featureFactory } from '@/tests/factories/2014/feature.factory'\nimport { levelFactory } from '@/tests/factories/2014/level.factory'\nimport { subclassFactory } from '@/tests/factories/2014/subclass.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('subclass')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\n// Setup cleanup for all relevant models\nsetupModelCleanup(SubclassModel)\nsetupModelCleanup(LevelModel)\nsetupModelCleanup(FeatureModel)\n\ndescribe('SubclassController', () => {\n  describe('index', () => {\n    it('returns a list of subclasses', async () => {\n      // Arrange\n      const subclassesData = subclassFactory.buildList(3)\n      await SubclassModel.insertMany(subclassesData)\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      // Act\n      await SubclassController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          // Index returns index, name, url\n          expect.objectContaining({\n            index: subclassesData[0].index,\n            name: subclassesData[0].name,\n            url: subclassesData[0].url\n          }),\n          expect.objectContaining({\n            index: subclassesData[1].index,\n            name: subclassesData[1].name,\n            url: subclassesData[1].url\n          }),\n          expect.objectContaining({\n            index: subclassesData[2].index,\n            name: subclassesData[2].name,\n            url: subclassesData[2].url\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('handles database errors during find', async () => {\n      // Arrange\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n      const error = new Error('Database find failed')\n      vi.spyOn(SubclassModel, 'find').mockImplementationOnce(() => {\n        const query = {\n          select: vi.fn().mockReturnThis(),\n          sort: vi.fn().mockReturnThis(),\n          exec: vi.fn().mockRejectedValueOnce(error)\n        } as any\n        return query\n      })\n\n      // Act\n      await SubclassController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n\n    it('returns an empty list when no subclasses exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n      await SubclassController.index(request, response, mockNext)\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single subclass when found', async () => {\n      // Arrange\n      const subclassData = subclassFactory.build({ index: 'berserker' })\n      await SubclassModel.insertMany([subclassData])\n      const request = createRequest({ params: { index: 'berserker' } })\n      const response = createResponse()\n\n      // Act\n      await SubclassController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData).toMatchObject({\n        index: subclassData.index,\n        name: subclassData.name,\n        url: subclassData.url\n      })\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the subclass is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n      await SubclassController.show(request, response, mockNext)\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n\n    it('handles database errors during findOne', async () => {\n      const request = createRequest({ params: { index: 'berserker' } })\n      const response = createResponse()\n      const error = new Error('Database findOne failed')\n      vi.spyOn(SubclassModel, 'findOne').mockRejectedValueOnce(error)\n      await SubclassController.show(request, response, mockNext)\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n  })\n\n  describe('showLevelsForSubclass', () => {\n    const subclassIndex = 'berserker'\n    const subclassUrl = `/api/2014/subclasses/${subclassIndex}`\n    it('returns levels for a specific subclass', async () => {\n      // Arrange\n      const subclassData = subclassFactory.build({ index: subclassIndex, url: subclassUrl })\n      await SubclassModel.insertMany([subclassData])\n      // Create levels specifically linked to this subclass\n      const levelsData = levelFactory.buildList(3, {\n        subclass: { index: subclassIndex, name: subclassData.name, url: subclassUrl }\n      })\n      await LevelModel.insertMany(levelsData)\n      // Add some unrelated levels to ensure filtering works\n      await LevelModel.insertMany(levelFactory.buildList(2))\n\n      const request = createRequest({ params: { index: subclassIndex } })\n      const response = createResponse()\n\n      // Act\n      await SubclassController.showLevelsForSubclass(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData).toBeInstanceOf(Array)\n      expect(responseData).toHaveLength(3) // Only the 3 levels for this subclass\n      // Check if the returned levels match the ones created for the subclass\n      expect(responseData).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({\n            index: levelsData[0].index,\n            level: levelsData[0].level,\n            url: levelsData[0].url\n          }),\n          expect.objectContaining({\n            index: levelsData[1].index,\n            level: levelsData[1].level,\n            url: levelsData[1].url\n          }),\n          expect.objectContaining({\n            index: levelsData[2].index,\n            level: levelsData[2].level,\n            url: levelsData[2].url\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty array if subclass exists but has no levels', async () => {\n      const subclassData = subclassFactory.build({ index: subclassIndex })\n      await SubclassModel.insertMany([subclassData])\n      // No levels inserted for this subclass\n      const request = createRequest({ params: { index: subclassIndex } })\n      const response = createResponse()\n      await SubclassController.showLevelsForSubclass(request, response, mockNext)\n      expect(response.statusCode).toBe(200)\n      expect(JSON.parse(response._getData())).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty array for a non-existent subclass index', async () => {\n      const request = createRequest({ params: { index: 'nonexistent-subclass' } })\n      const response = createResponse()\n      await SubclassController.showLevelsForSubclass(request, response, mockNext)\n      expect(response.statusCode).toBe(200)\n      expect(JSON.parse(response._getData())).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('handles database errors during level find', async () => {\n      const request = createRequest({ params: { index: subclassIndex } })\n      const response = createResponse()\n      const error = new Error('Level find failed')\n      vi.spyOn(LevelModel, 'find').mockImplementationOnce(\n        () =>\n          ({\n            sort: vi.fn().mockRejectedValueOnce(error)\n          }) as any\n      )\n      await SubclassController.showLevelsForSubclass(request, response, mockNext)\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n  })\n\n  describe('showLevelForSubclass', () => {\n    const subclassIndex = 'berserker'\n    const targetLevel = 5\n    const levelUrl = `/api/2014/subclasses/${subclassIndex}/levels/${targetLevel}`\n\n    it('returns a specific level for a subclass', async () => {\n      // Arrange\n      const subclassData = subclassFactory.build({ index: subclassIndex })\n      await SubclassModel.insertMany([subclassData])\n      const levelData = levelFactory.build({\n        level: targetLevel,\n        subclass: {\n          index: subclassIndex,\n          name: subclassData.name,\n          url: `/api/2014/subclasses/${subclassIndex}`\n        },\n        url: levelUrl\n      })\n      await LevelModel.insertMany([levelData])\n\n      const request = createRequest({\n        params: { index: subclassIndex, level: String(targetLevel) }\n      })\n      const response = createResponse()\n\n      // Act\n      await SubclassController.showLevelForSubclass(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData).toMatchObject({\n        index: levelData.index,\n        level: levelData.level,\n        url: levelData.url\n      })\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the specific level is not found for the subclass', async () => {\n      const request = createRequest({ params: { index: subclassIndex, level: '10' } })\n      const response = createResponse()\n      await SubclassController.showLevelForSubclass(request, response, mockNext)\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n\n    it('handles database errors during level findOne', async () => {\n      const request = createRequest({\n        params: { index: subclassIndex, level: String(targetLevel) }\n      })\n      const response = createResponse()\n      const error = new Error('Level findOne failed')\n      vi.spyOn(LevelModel, 'findOne').mockRejectedValueOnce(error)\n      await SubclassController.showLevelForSubclass(request, response, mockNext)\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n\n    it('returns 400 for invalid level parameter (non-numeric)', async () => {\n      const request = createRequest({ params: { index: subclassIndex, level: 'invalid' } })\n      const response = createResponse()\n      await SubclassController.showLevelForSubclass(request, response, mockNext)\n      expect(response.statusCode).toBe(400)\n      expect(JSON.parse(response._getData()).error).toContain('Invalid path parameters')\n      expect(mockNext).not.toHaveBeenCalled() // Should not call next on validation error\n    })\n\n    it('returns 400 for invalid level parameter (out of range)', async () => {\n      const request = createRequest({ params: { index: subclassIndex, level: '0' } }) // Level 0 is invalid\n      const response = createResponse()\n      await SubclassController.showLevelForSubclass(request, response, mockNext)\n      expect(response.statusCode).toBe(400)\n      expect(JSON.parse(response._getData()).error).toContain('Invalid path parameters')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('showFeaturesForSubclass', () => {\n    const subclassIndex = 'berserker'\n    const subclassUrl = `/api/2014/subclasses/${subclassIndex}`\n\n    it('returns features for a specific subclass', async () => {\n      // Arrange\n      const subclassData = subclassFactory.build({ index: subclassIndex, url: subclassUrl })\n      await SubclassModel.insertMany([subclassData])\n      // Create features linked to this subclass\n      const subclassRef = apiReferenceFactory.build({\n        index: subclassIndex,\n        name: subclassData.name,\n        url: subclassUrl\n      })\n      const featuresData = featureFactory.buildList(2, { subclass: subclassRef })\n      await FeatureModel.insertMany(featuresData)\n      // Add unrelated features\n      await FeatureModel.insertMany(featureFactory.buildList(1))\n\n      const request = createRequest({ params: { index: subclassIndex } })\n      const response = createResponse()\n\n      // Act\n      await SubclassController.showFeaturesForSubclass(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData).toHaveProperty('count', 2)\n      expect(responseData).toHaveProperty('results')\n      expect(responseData.results).toBeInstanceOf(Array)\n      expect(responseData.results).toHaveLength(2) // Only the 2 features for this subclass\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({\n            index: featuresData[0].index,\n            name: featuresData[0].name,\n            url: featuresData[0].url\n          }),\n          expect.objectContaining({\n            index: featuresData[1].index,\n            name: featuresData[1].name,\n            url: featuresData[1].url\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list if subclass has no features', async () => {\n      const subclassData = subclassFactory.build({ index: subclassIndex })\n      await SubclassModel.insertMany([subclassData])\n      // No features inserted\n      const request = createRequest({ params: { index: subclassIndex } })\n      const response = createResponse()\n      await SubclassController.showFeaturesForSubclass(request, response, mockNext)\n      expect(response.statusCode).toBe(200)\n      expect(JSON.parse(response._getData())).toEqual({ count: 0, results: [] })\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('handles database errors during feature find', async () => {\n      const request = createRequest({ params: { index: subclassIndex } })\n      const response = createResponse()\n      const error = new Error('Feature find failed')\n      vi.spyOn(FeatureModel, 'find').mockImplementationOnce(() => {\n        const query = {\n          select: vi.fn().mockReturnThis(),\n          sort: vi.fn().mockRejectedValueOnce(error)\n        } as any\n        return query\n      })\n      await SubclassController.showFeaturesForSubclass(request, response, mockNext)\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n  })\n\n  describe('showFeaturesForSubclassAndLevel', () => {\n    const subclassIndex = 'berserker'\n    const targetLevel = 3\n    const subclassUrl = `/api/2014/subclasses/${subclassIndex}`\n\n    it('returns features for a specific subclass and level', async () => {\n      // Arrange\n      const subclassData = subclassFactory.build({ index: subclassIndex, url: subclassUrl })\n      await SubclassModel.insertMany([subclassData])\n      const subclassRef = apiReferenceFactory.build({\n        index: subclassIndex,\n        name: subclassData.name,\n        url: subclassUrl\n      })\n\n      // Features at the target level\n      const featuresDataLevel = featureFactory.buildList(2, {\n        subclass: subclassRef,\n        level: targetLevel\n      })\n      // Features at a different level\n      const featuresDataOtherLevel = featureFactory.buildList(1, {\n        subclass: subclassRef,\n        level: targetLevel + 1\n      })\n      await FeatureModel.insertMany([...featuresDataLevel, ...featuresDataOtherLevel])\n\n      const request = createRequest({\n        params: { index: subclassIndex, level: String(targetLevel) }\n      })\n      const response = createResponse()\n\n      // Act\n      await SubclassController.showFeaturesForSubclassAndLevel(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData).toHaveProperty('count', 2)\n      expect(responseData).toHaveProperty('results')\n      expect(responseData.results).toBeInstanceOf(Array)\n      expect(responseData.results).toHaveLength(2) // Only features at target level\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({\n            index: featuresDataLevel[0].index,\n            name: featuresDataLevel[0].name,\n            url: featuresDataLevel[0].url\n          }),\n          expect.objectContaining({\n            index: featuresDataLevel[1].index,\n            name: featuresDataLevel[1].name,\n            url: featuresDataLevel[1].url\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list if no features for the specific level', async () => {\n      const subclassData = subclassFactory.build({ index: subclassIndex })\n      await SubclassModel.insertMany([subclassData])\n      // No features at target level inserted\n      const request = createRequest({\n        params: { index: subclassIndex, level: String(targetLevel) }\n      })\n      const response = createResponse()\n      await SubclassController.showFeaturesForSubclassAndLevel(request, response, mockNext)\n      expect(response.statusCode).toBe(200)\n      expect(JSON.parse(response._getData())).toEqual({ count: 0, results: [] })\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('handles database errors during feature find for level', async () => {\n      const request = createRequest({\n        params: { index: subclassIndex, level: String(targetLevel) }\n      })\n      const response = createResponse()\n      const error = new Error('Feature find failed')\n      vi.spyOn(FeatureModel, 'find').mockImplementationOnce(() => {\n        const query = {\n          select: vi.fn().mockReturnThis(),\n          sort: vi.fn().mockRejectedValueOnce(error)\n        } as any\n        return query\n      })\n      await SubclassController.showFeaturesForSubclassAndLevel(request, response, mockNext)\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n\n    it('returns 400 for invalid level parameter (non-numeric)', async () => {\n      const request = createRequest({ params: { index: subclassIndex, level: 'invalid' } })\n      const response = createResponse()\n      await SubclassController.showFeaturesForSubclassAndLevel(request, response, mockNext)\n      expect(response.statusCode).toBe(400)\n      expect(JSON.parse(response._getData()).error).toContain('Invalid path parameters')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns 400 for invalid level parameter (out of range)', async () => {\n      const request = createRequest({ params: { index: subclassIndex, level: '21' } })\n      const response = createResponse()\n      await SubclassController.showFeaturesForSubclassAndLevel(request, response, mockNext)\n      expect(response.statusCode).toBe(400)\n      expect(JSON.parse(response._getData()).error).toContain('Invalid path parameters')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/subraceController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport * as SubraceController from '@/controllers/api/2014/subraceController'\nimport ProficiencyModel from '@/models/2014/proficiency'\nimport SubraceModel from '@/models/2014/subrace'\nimport TraitModel from '@/models/2014/trait'\nimport { proficiencyFactory } from '@/tests/factories/2014/proficiency.factory'\nimport { subraceFactory } from '@/tests/factories/2014/subrace.factory'\nimport { traitFactory } from '@/tests/factories/2014/trait.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Setup DB isolation\nconst dbUri = generateUniqueDbUri('subrace')\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\n// Cleanup all relevant models\nsetupModelCleanup(SubraceModel)\nsetupModelCleanup(TraitModel)\nsetupModelCleanup(ProficiencyModel)\n\ndescribe('SubraceController', () => {\n  describe('index', () => {\n    it('returns a list of subraces', async () => {\n      // Arrange: Seed DB\n      const subracesData = subraceFactory.buildList(3)\n      await SubraceModel.insertMany(subracesData)\n\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      // Act\n      await SubraceController.index(request, response, mockNext)\n\n      // Assert: Check response based on seeded data\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({ index: subracesData[0].index, name: subracesData[0].name }),\n          expect.objectContaining({ index: subracesData[1].index, name: subracesData[1].name }),\n          expect.objectContaining({ index: subracesData[2].index, name: subracesData[2].name })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    // describe('when something goes wrong', ...)\n  })\n\n  describe('show', () => {\n    it('returns a single subrace', async () => {\n      // Arrange\n      const subraceData = subraceFactory.build({ index: 'high-elf' })\n      await SubraceModel.insertMany([subraceData])\n\n      const request = createRequest({ params: { index: 'high-elf' } })\n      const response = createResponse()\n\n      // Act\n      await SubraceController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe(subraceData.index)\n      expect(responseData.name).toBe(subraceData.name)\n      // Add more checks as needed\n      expect(responseData.race.index).toBe(subraceData.race.index)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the record does not exist', async () => {\n      // Arrange\n      const request = createRequest({ params: { index: 'non-existent' } })\n      const response = createResponse()\n      // Act\n      await SubraceController.show(request, response, mockNext)\n      // Assert\n      expect(mockNext).toHaveBeenCalledWith()\n      expect(response._getData()).toBe('')\n    })\n\n    // describe('when something goes wrong', ...)\n  })\n\n  describe('showTraitsForSubrace', () => {\n    const subraceIndex = 'rock-gnome'\n    const subraceUrl = `/api/2014/subraces/${subraceIndex}`\n\n    it('returns a list of traits for the subrace', async () => {\n      // Arrange\n      const subraceRef = { index: subraceIndex, name: 'Rock Gnome', url: subraceUrl }\n      const traitsData = traitFactory.buildList(2, { subraces: [subraceRef] })\n      await TraitModel.insertMany(traitsData)\n      // Seed unrelated traits\n      await TraitModel.insertMany(traitFactory.buildList(1))\n\n      const request = createRequest({ params: { index: subraceIndex } })\n      const response = createResponse()\n\n      // Act\n      await SubraceController.showTraitsForSubrace(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(2)\n      expect(responseData.results).toHaveLength(2)\n      expect(responseData.results.map((t: any) => t.index)).toEqual(\n        expect.arrayContaining([traitsData[0].index, traitsData[1].index])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    // describe('when something goes wrong', ...)\n  })\n\n  describe('showProficienciesForSubrace', () => {\n    const subraceIndex = 'hill-dwarf'\n    const subraceUrl = `/api/2014/subraces/${subraceIndex}`\n\n    it('returns a list of proficiencies for the subrace', async () => {\n      // Arrange\n      const subraceRef = { index: subraceIndex, name: 'Hill Dwarf', url: subraceUrl }\n      // The Proficiency model links via `races` which includes subraces conceptually\n      const proficienciesData = proficiencyFactory.buildList(3, { races: [subraceRef] })\n      await ProficiencyModel.insertMany(proficienciesData)\n      // Seed unrelated proficiencies\n      await ProficiencyModel.insertMany(proficiencyFactory.buildList(2))\n\n      const request = createRequest({ params: { index: subraceIndex } })\n      const response = createResponse()\n\n      // Act\n      await SubraceController.showProficienciesForSubrace(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results.map((p: any) => p.index)).toEqual(\n        expect.arrayContaining([\n          proficienciesData[0].index,\n          proficienciesData[1].index,\n          proficienciesData[2].index\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    // describe('when something goes wrong', ...)\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/traitController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport TraitController from '@/controllers/api/2014/traitController'\nimport TraitModel from '@/models/2014/trait'\nimport { traitFactory } from '@/tests/factories/2014/trait.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('trait')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(TraitModel)\n\ndescribe('TraitController', () => {\n  describe('index', () => {\n    it('returns a list of traits', async () => {\n      // Arrange\n      const traitsData = traitFactory.buildList(3)\n      await TraitModel.insertMany(traitsData)\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      // Act\n      await TraitController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({ index: traitsData[0].index, name: traitsData[0].name }),\n          expect.objectContaining({ index: traitsData[1].index, name: traitsData[1].name }),\n          expect.objectContaining({ index: traitsData[2].index, name: traitsData[2].name })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no traits exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await TraitController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single trait when found', async () => {\n      // Arrange\n      const traitData = traitFactory.build({ index: 'darkvision', name: 'Darkvision' })\n      await TraitModel.insertMany([traitData])\n      const request = createRequest({ params: { index: 'darkvision' } })\n      const response = createResponse()\n\n      // Act\n      await TraitController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('darkvision')\n      expect(responseData.name).toBe('Darkvision')\n      expect(responseData.desc).toEqual(traitData.desc)\n      // Add checks for potentially optional fields like races, subraces, proficiencies if needed\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the trait is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await TraitController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200) // Passes to next()\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith() // Default 404 handling\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2014/weaponPropertyController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport WeaponPropertyController from '@/controllers/api/2014/weaponPropertyController'\nimport WeaponPropertyModel from '@/models/2014/weaponProperty'\nimport { weaponPropertyFactory } from '@/tests/factories/2014/weaponProperty.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Setup DB isolation\nconst dbUri = generateUniqueDbUri('weaponproperty')\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(WeaponPropertyModel)\n\ndescribe('WeaponPropertyController', () => {\n  describe('index', () => {\n    it('returns a list of weapon properties', async () => {\n      // Arrange: Seed DB\n      const propertiesData = weaponPropertyFactory.buildList(3)\n      await WeaponPropertyModel.insertMany(propertiesData)\n\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      // Act\n      await WeaponPropertyController.index(request, response, mockNext)\n\n      // Assert: Check response based on seeded data\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({ index: propertiesData[0].index, name: propertiesData[0].name }),\n          expect.objectContaining({ index: propertiesData[1].index, name: propertiesData[1].name }),\n          expect.objectContaining({ index: propertiesData[2].index, name: propertiesData[2].name })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    // describe('when something goes wrong', ...)\n  })\n\n  describe('show', () => {\n    it('returns a single weapon property', async () => {\n      // Arrange\n      const propertyData = weaponPropertyFactory.build({ index: 'versatile' })\n      await WeaponPropertyModel.insertMany([propertyData])\n\n      const request = createRequest({ params: { index: 'versatile' } })\n      const response = createResponse()\n\n      // Act\n      await WeaponPropertyController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe(propertyData.index)\n      expect(responseData.name).toBe(propertyData.name)\n      expect(responseData.desc).toEqual(propertyData.desc)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the record does not exist', async () => {\n      // Arrange\n      const request = createRequest({ params: { index: 'non-existent' } })\n      const response = createResponse()\n      // Act\n      await WeaponPropertyController.show(request, response, mockNext)\n      // Assert\n      expect(mockNext).toHaveBeenCalledWith()\n      expect(response._getData()).toBe('')\n    })\n\n    // describe('when something goes wrong', ...)\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/BackgroundController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport BackgroundController from '@/controllers/api/2024/backgroundController'\nimport BackgroundModel from '@/models/2024/background'\nimport { backgroundFactory } from '@/tests/factories/2024/background.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\nconst dbUri = generateUniqueDbUri('background')\n\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(BackgroundModel)\n\ndescribe('BackgroundController', () => {\n  describe('index', () => {\n    it('returns a list of backgrounds', async () => {\n      const backgroundsData = backgroundFactory.buildList(3)\n      await BackgroundModel.insertMany(backgroundsData)\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await BackgroundController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('filters by name', async () => {\n      const backgroundsData = [\n        backgroundFactory.build({ name: 'Acolyte' }),\n        backgroundFactory.build({ name: 'Criminal' })\n      ]\n      await BackgroundModel.insertMany(backgroundsData)\n      const request = createRequest({ query: { name: 'Acolyte' } })\n      const response = createResponse()\n\n      await BackgroundController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(1)\n      expect(responseData.results[0].name).toBe('Acolyte')\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single background when found', async () => {\n      const backgroundData = backgroundFactory.build({ index: 'acolyte', name: 'Acolyte' })\n      await BackgroundModel.insertMany([backgroundData])\n      const request = createRequest({ params: { index: 'acolyte' } })\n      const response = createResponse()\n\n      await BackgroundController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('acolyte')\n      expect(responseData.name).toBe('Acolyte')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the background is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await BackgroundController.show(request, response, mockNext)\n\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/FeatController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport FeatController from '@/controllers/api/2024/featController'\nimport FeatModel from '@/models/2024/feat'\nimport { featFactory } from '@/tests/factories/2024/feat.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\nconst dbUri = generateUniqueDbUri('feat')\n\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(FeatModel)\n\ndescribe('FeatController', () => {\n  describe('index', () => {\n    it('returns a list of feats', async () => {\n      const featsData = featFactory.buildList(3)\n      await FeatModel.insertMany(featsData)\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await FeatController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('filters by name', async () => {\n      const featsData = [featFactory.build({ name: 'Alert' }), featFactory.build({ name: 'Lucky' })]\n      await FeatModel.insertMany(featsData)\n      const request = createRequest({ query: { name: 'Alert' } })\n      const response = createResponse()\n\n      await FeatController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(1)\n      expect(responseData.results[0].name).toBe('Alert')\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single feat when found', async () => {\n      const featData = featFactory.build({ index: 'alert', name: 'Alert' })\n      await FeatModel.insertMany([featData])\n      const request = createRequest({ params: { index: 'alert' } })\n      const response = createResponse()\n\n      await FeatController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('alert')\n      expect(responseData.name).toBe('Alert')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the feat is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await FeatController.show(request, response, mockNext)\n\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/MagicItemController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport MagicItemController from '@/controllers/api/2024/magicItemController'\nimport MagicItemModel from '@/models/2024/magicItem'\nimport { magicItemFactory } from '@/tests/factories/2024/magicItem.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\nconst dbUri = generateUniqueDbUri('magic-item')\n\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(MagicItemModel)\n\ndescribe('MagicItemController', () => {\n  describe('index', () => {\n    it('returns a list of magic items', async () => {\n      const itemsData = magicItemFactory.buildList(3)\n      await MagicItemModel.insertMany(itemsData)\n\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await MagicItemController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('filters by name', async () => {\n      const itemsData = [\n        magicItemFactory.build({ name: 'Bag of Holding' }),\n        magicItemFactory.build({ name: 'Cloak of Elvenkind' })\n      ]\n      await MagicItemModel.insertMany(itemsData)\n\n      const request = createRequest({ query: { name: 'Bag' } })\n      const response = createResponse()\n\n      await MagicItemController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(1)\n      expect(responseData.results[0].name).toBe('Bag of Holding')\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single magic item when found', async () => {\n      const itemData = magicItemFactory.build({ index: 'bag-of-holding', name: 'Bag of Holding' })\n      await MagicItemModel.insertMany([itemData])\n\n      const request = createRequest({ params: { index: 'bag-of-holding' } })\n      const response = createResponse()\n\n      await MagicItemController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('bag-of-holding')\n      expect(responseData.name).toBe('Bag of Holding')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the magic item is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await MagicItemController.show(request, response, mockNext)\n\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/ProficiencyController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport ProficiencyController from '@/controllers/api/2024/proficiencyController'\nimport ProficiencyModel from '@/models/2024/proficiency'\nimport { proficiencyFactory } from '@/tests/factories/2024/proficiency.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\nconst dbUri = generateUniqueDbUri('proficiency')\n\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(ProficiencyModel)\n\ndescribe('ProficiencyController', () => {\n  describe('index', () => {\n    it('returns a list of proficiencies', async () => {\n      const proficienciesData = proficiencyFactory.buildList(3)\n      await ProficiencyModel.insertMany(proficienciesData)\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await ProficiencyController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('filters by name', async () => {\n      const proficienciesData = [\n        proficiencyFactory.build({ name: 'Skill: Arcana' }),\n        proficiencyFactory.build({ name: 'Skill: History' })\n      ]\n      await ProficiencyModel.insertMany(proficienciesData)\n      const request = createRequest({ query: { name: 'Arcana' } })\n      const response = createResponse()\n\n      await ProficiencyController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(1)\n      expect(responseData.results[0].name).toBe('Skill: Arcana')\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single proficiency when found', async () => {\n      const proficiencyData = proficiencyFactory.build({\n        index: 'skill-arcana',\n        name: 'Skill: Arcana'\n      })\n      await ProficiencyModel.insertMany([proficiencyData])\n      const request = createRequest({ params: { index: 'skill-arcana' } })\n      const response = createResponse()\n\n      await ProficiencyController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('skill-arcana')\n      expect(responseData.name).toBe('Skill: Arcana')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the proficiency is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await ProficiencyController.show(request, response, mockNext)\n\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/SubclassController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport SubclassController from '@/controllers/api/2024/subclassController'\nimport SubclassModel from '@/models/2024/subclass'\nimport { subclassFactory } from '@/tests/factories/2024/subclass.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\nconst dbUri = generateUniqueDbUri('subclass')\n\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(SubclassModel)\n\ndescribe('SubclassController', () => {\n  describe('index', () => {\n    it('returns a list of subclasses', async () => {\n      const subclassesData = subclassFactory.buildList(3)\n      await SubclassModel.insertMany(subclassesData)\n\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await SubclassController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('filters by name', async () => {\n      const subclassesData = [\n        subclassFactory.build({ name: 'Path of the Berserker' }),\n        subclassFactory.build({ name: 'Path of the Totem Warrior' })\n      ]\n      await SubclassModel.insertMany(subclassesData)\n\n      const request = createRequest({ query: { name: 'Berserker' } })\n      const response = createResponse()\n\n      await SubclassController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(1)\n      expect(responseData.results[0].name).toBe('Path of the Berserker')\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single subclass when found', async () => {\n      const subclassData = subclassFactory.build({\n        index: 'berserker',\n        name: 'Path of the Berserker'\n      })\n      await SubclassModel.insertMany([subclassData])\n\n      const request = createRequest({ params: { index: 'berserker' } })\n      const response = createResponse()\n\n      await SubclassController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('berserker')\n      expect(responseData.name).toBe('Path of the Berserker')\n      expect(responseData.features).toHaveLength(2)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the subclass is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await SubclassController.show(request, response, mockNext)\n\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/abilityScoreController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport AbilityScoreController from '@/controllers/api/2024/abilityScoreController'\nimport AbilityScoreModel from '@/models/2024/abilityScore'\nimport { abilityScoreFactory } from '@/tests/factories/2024/abilityScore.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('abilityscore')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(AbilityScoreModel)\n\ndescribe('AbilityScoreController', () => {\n  describe('index', () => {\n    it('returns a list of ability scores', async () => {\n      // Arrange: Seed the database\n      const abilityScoresData = abilityScoreFactory.buildList(3)\n      await AbilityScoreModel.insertMany(abilityScoresData)\n\n      const request = createRequest()\n      const response = createResponse()\n\n      // Act\n      await AbilityScoreController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results.length).toBe(3)\n      // Check if the returned data loosely matches the seeded data (checking name/index)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({\n            index: abilityScoresData[0].index,\n            name: abilityScoresData[0].name\n          }),\n          expect.objectContaining({\n            index: abilityScoresData[1].index,\n            name: abilityScoresData[1].name\n          }),\n          expect.objectContaining({\n            index: abilityScoresData[2].index,\n            name: abilityScoresData[2].name\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    // Skipping the explicit 'find error' mock test for now.\n  })\n\n  describe('show', () => {\n    it('returns a single ability score when found', async () => {\n      // Arrange: Seed the database\n      const abilityScoreData = abilityScoreFactory.build({ index: 'cha', name: 'CHA' })\n      await AbilityScoreModel.insertMany([abilityScoreData])\n\n      const request = createRequest({ params: { index: 'cha' } })\n      const response = createResponse()\n\n      // Act\n      await AbilityScoreController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('cha')\n      expect(responseData.name).toBe('CHA')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next with an error if the ability score is not found', async () => {\n      // Arrange: Database is empty (guaranteed by setupModelCleanup)\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      // Act\n      await AbilityScoreController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200) // Default node-mocks-http status\n      expect(response._getData()).toBe('') // No data written before error\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith() // Expect next() called with no arguments\n    })\n\n    // Skipping the explicit 'findOne error' mock test for similar reasons as above.\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/alignmentController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport AlignmentController from '@/controllers/api/2024/alignmentController'\nimport AlignmentModel from '@/models/2024/alignment'\nimport { alignmentFactory } from '@/tests/factories/2024/alignment.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\nconst dbUri = generateUniqueDbUri('alignment')\n\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(AlignmentModel)\n\ndescribe('AlignmentController', () => {\n  describe('index', () => {\n    it('returns a list of alignments', async () => {\n      const alignmentsData = alignmentFactory.buildList(3)\n      await AlignmentModel.insertMany(alignmentsData)\n\n      const request = createRequest()\n      const response = createResponse()\n\n      await AlignmentController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results.length).toBe(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({\n            index: alignmentsData[0].index,\n            name: alignmentsData[0].name\n          }),\n          expect.objectContaining({\n            index: alignmentsData[1].index,\n            name: alignmentsData[1].name\n          }),\n          expect.objectContaining({\n            index: alignmentsData[2].index,\n            name: alignmentsData[2].name\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single alignment when found', async () => {\n      const alignmentData = alignmentFactory.build({ index: 'lawful-good', name: 'Lawful Good' })\n      await AlignmentModel.insertMany([alignmentData])\n\n      const request = createRequest({ params: { index: 'lawful-good' } })\n      const response = createResponse()\n\n      await AlignmentController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('lawful-good')\n      expect(responseData.name).toBe('Lawful Good')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next with an error if the alignment is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await AlignmentController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/conditionController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport ConditionController from '@/controllers/api/2024/conditionController'\nimport ConditionModel from '@/models/2024/condition'\nimport { conditionFactory } from '@/tests/factories/2024/condition.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\nconst dbUri = generateUniqueDbUri('condition')\n\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(ConditionModel)\n\ndescribe('ConditionController', () => {\n  describe('index', () => {\n    it('returns a list of conditions', async () => {\n      const conditionsData = conditionFactory.buildList(3)\n      await ConditionModel.insertMany(conditionsData)\n\n      const request = createRequest()\n      const response = createResponse()\n\n      await ConditionController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results.length).toBe(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({\n            index: conditionsData[0].index,\n            name: conditionsData[0].name\n          }),\n          expect.objectContaining({\n            index: conditionsData[1].index,\n            name: conditionsData[1].name\n          }),\n          expect.objectContaining({\n            index: conditionsData[2].index,\n            name: conditionsData[2].name\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single condition when found', async () => {\n      const conditionData = conditionFactory.build({ index: 'poisoned', name: 'Poisoned' })\n      await ConditionModel.insertMany([conditionData])\n\n      const request = createRequest({ params: { index: 'poisoned' } })\n      const response = createResponse()\n\n      await ConditionController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('poisoned')\n      expect(responseData.name).toBe('Poisoned')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next with an error if the condition is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await ConditionController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/damageTypeController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport DamageTypeController from '@/controllers/api/2024/damageTypeController'\nimport DamageTypeModel from '@/models/2024/damageType'\nimport { damageTypeFactory } from '@/tests/factories/2024/damageType.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\nconst dbUri = generateUniqueDbUri('damagetype')\n\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(DamageTypeModel)\n\ndescribe('DamageTypeController', () => {\n  describe('index', () => {\n    it('returns a list of damage types', async () => {\n      const damageTypesData = damageTypeFactory.buildList(3)\n      await DamageTypeModel.insertMany(damageTypesData)\n\n      const request = createRequest()\n      const response = createResponse()\n\n      await DamageTypeController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results.length).toBe(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({\n            index: damageTypesData[0].index,\n            name: damageTypesData[0].name\n          }),\n          expect.objectContaining({\n            index: damageTypesData[1].index,\n            name: damageTypesData[1].name\n          }),\n          expect.objectContaining({\n            index: damageTypesData[2].index,\n            name: damageTypesData[2].name\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single damage type when found', async () => {\n      const damageTypeData = damageTypeFactory.build({ index: 'fire', name: 'Fire' })\n      await DamageTypeModel.insertMany([damageTypeData])\n\n      const request = createRequest({ params: { index: 'fire' } })\n      const response = createResponse()\n\n      await DamageTypeController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('fire')\n      expect(responseData.name).toBe('Fire')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next with an error if the damage type is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await DamageTypeController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/equipmentCategoryController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport EquipmentCategoryController from '@/controllers/api/2024/equipmentCategoryController'\nimport EquipmentCategoryModel from '@/models/2024/equipmentCategory'\nimport { equipmentCategoryFactory } from '@/tests/factories/2024/equipmentCategory.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('equipmentcategory')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(EquipmentCategoryModel)\n\ndescribe('EquipmentCategoryController', () => {\n  describe('index', () => {\n    it('returns a list of equipment categories', async () => {\n      const categoriesData = equipmentCategoryFactory.buildList(3)\n      const categoryDocs = categoriesData.map((data) => new EquipmentCategoryModel(data))\n      await EquipmentCategoryModel.insertMany(categoryDocs)\n\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await EquipmentCategoryController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({ index: categoriesData[0].index, name: categoriesData[0].name }),\n          expect.objectContaining({ index: categoriesData[1].index, name: categoriesData[1].name }),\n          expect.objectContaining({ index: categoriesData[2].index, name: categoriesData[2].name })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no equipment categories exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await EquipmentCategoryController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single equipment category when found', async () => {\n      const categoryData = equipmentCategoryFactory.build({ index: 'armor', name: 'Armor' })\n      await EquipmentCategoryModel.insertMany([categoryData])\n\n      const request = createRequest({ params: { index: 'armor' } })\n      const response = createResponse()\n\n      await EquipmentCategoryController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('armor')\n      expect(responseData.name).toBe('Armor')\n      expect(responseData.equipment).toHaveLength(categoryData.equipment?.length ?? 0)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the equipment category is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await EquipmentCategoryController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/equipmentController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport EquipmentController from '@/controllers/api/2024/equipmentController'\nimport EquipmentModel from '@/models/2024/equipment'\nimport { equipmentFactory, weaponFactory } from '@/tests/factories/2024/equipment.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('equipment')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(EquipmentModel)\n\ndescribe('EquipmentController', () => {\n  describe('index', () => {\n    it('returns a list of equipment', async () => {\n      // Arrange: Seed the database with different equipment types\n      const equipmentData = equipmentFactory.buildList(2)\n      const weaponData = weaponFactory.build()\n      await EquipmentModel.insertMany([...equipmentData, weaponData])\n\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      // Act\n      await EquipmentController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({ index: equipmentData[0].index, name: equipmentData[0].name }),\n          expect.objectContaining({ index: equipmentData[1].index, name: equipmentData[1].name }),\n          expect.objectContaining({ index: weaponData.index, name: weaponData.name })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no equipment exists', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await EquipmentController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single equipment item when found', async () => {\n      const equipmentData = weaponFactory.build({\n        index: 'longsword',\n        name: 'Longsword'\n      })\n      await EquipmentModel.insertMany([equipmentData])\n\n      const request = createRequest({ params: { index: 'longsword' } })\n      const response = createResponse()\n\n      await EquipmentController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('longsword')\n      expect(responseData.name).toBe('Longsword')\n      expect(responseData.damage).toBeDefined()\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the equipment is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await EquipmentController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200) // Default status\n      expect(response._getData()).toBe('') // No data written\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/languageController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport LanguageController from '@/controllers/api/2024/languageController'\nimport LanguageModel from '@/models/2024/language'\nimport { languageFactory } from '@/tests/factories/2024/language.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\nconst dbUri = generateUniqueDbUri('language')\n\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(LanguageModel)\n\ndescribe('LanguageController', () => {\n  describe('index', () => {\n    it('returns a list of languages', async () => {\n      const languagesData = languageFactory.buildList(3)\n      await LanguageModel.insertMany(languagesData)\n\n      const request = createRequest()\n      const response = createResponse()\n\n      await LanguageController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results.length).toBe(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({\n            index: languagesData[0].index,\n            name: languagesData[0].name\n          }),\n          expect.objectContaining({\n            index: languagesData[1].index,\n            name: languagesData[1].name\n          }),\n          expect.objectContaining({\n            index: languagesData[2].index,\n            name: languagesData[2].name\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single language when found', async () => {\n      const languageData = languageFactory.build({ index: 'common', name: 'Common' })\n      await LanguageModel.insertMany([languageData])\n\n      const request = createRequest({ params: { index: 'common' } })\n      const response = createResponse()\n\n      await LanguageController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('common')\n      expect(responseData.name).toBe('Common')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next with an error if the language is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await LanguageController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/magicSchoolController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport MagicSchoolController from '@/controllers/api/2024/magicSchoolController'\nimport MagicSchoolModel from '@/models/2024/magicSchool'\nimport { magicSchoolFactory } from '@/tests/factories/2024/magicSchool.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\nconst dbUri = generateUniqueDbUri('magicschool')\n\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(MagicSchoolModel)\n\ndescribe('MagicSchoolController', () => {\n  describe('index', () => {\n    it('returns a list of magic schools', async () => {\n      const magicSchoolsData = magicSchoolFactory.buildList(3)\n      await MagicSchoolModel.insertMany(magicSchoolsData)\n\n      const request = createRequest()\n      const response = createResponse()\n\n      await MagicSchoolController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results.length).toBe(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({\n            index: magicSchoolsData[0].index,\n            name: magicSchoolsData[0].name\n          }),\n          expect.objectContaining({\n            index: magicSchoolsData[1].index,\n            name: magicSchoolsData[1].name\n          }),\n          expect.objectContaining({\n            index: magicSchoolsData[2].index,\n            name: magicSchoolsData[2].name\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single magic school when found', async () => {\n      const magicSchoolData = magicSchoolFactory.build({ index: 'evocation', name: 'Evocation' })\n      await MagicSchoolModel.insertMany([magicSchoolData])\n\n      const request = createRequest({ params: { index: 'evocation' } })\n      const response = createResponse()\n\n      await MagicSchoolController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('evocation')\n      expect(responseData.name).toBe('Evocation')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next with an error if the magic school is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await MagicSchoolController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/skillController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport SkillController from '@/controllers/api/2024/skillController'\nimport SkillModel from '@/models/2024/skill' // Use Model suffix\nimport { skillFactory } from '@/tests/factories/2024/skill.factory' // Updated path\nimport { mockNext as defaultMockNext } from '@/tests/support' // Assuming support helper location\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('skill')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(SkillModel)\n\ndescribe('SkillController', () => {\n  describe('index', () => {\n    it('returns a list of skills', async () => {\n      // Arrange\n      const skillsData = skillFactory.buildList(3)\n      await SkillModel.insertMany(skillsData)\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      // Act\n      await SkillController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({ index: skillsData[0].index, name: skillsData[0].name }),\n          expect.objectContaining({ index: skillsData[1].index, name: skillsData[1].name }),\n          expect.objectContaining({ index: skillsData[2].index, name: skillsData[2].name })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no skills exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await SkillController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single skill when found', async () => {\n      // Arrange\n      const skillData = skillFactory.build({ index: 'athletics', name: 'Athletics' })\n      await SkillModel.insertMany([skillData])\n      const request = createRequest({ params: { index: 'athletics' } })\n      const response = createResponse()\n\n      // Act\n      await SkillController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('athletics')\n      expect(responseData.name).toBe('Athletics')\n      expect(responseData.description).toEqual(skillData.description)\n      // Check nested object\n      expect(responseData.ability_score.index).toEqual(skillData.ability_score.index)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the skill is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await SkillController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200) // Passes to next()\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith() // Default 404 handling\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/speciesController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport * as SpeciesController from '@/controllers/api/2024/speciesController'\nimport Species2024Model from '@/models/2024/species'\nimport Subspecies2024Model from '@/models/2024/subspecies'\nimport Trait2024Model from '@/models/2024/trait'\nimport { speciesFactory } from '@/tests/factories/2024/species.factory'\nimport { subspeciesFactory } from '@/tests/factories/2024/subspecies.factory'\nimport { traitFactory } from '@/tests/factories/2024/trait.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\nconst dbUri = generateUniqueDbUri('species2024')\n\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(Species2024Model)\nsetupModelCleanup(Subspecies2024Model)\nsetupModelCleanup(Trait2024Model)\n\ndescribe('SpeciesController (2024)', () => {\n  describe('index', () => {\n    it('returns a list of species', async () => {\n      const speciesData = speciesFactory.buildList(3)\n      await Species2024Model.insertMany(speciesData)\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await SpeciesController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no species exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await SpeciesController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single species when found', async () => {\n      const speciesData = speciesFactory.build({ index: 'elf', name: 'Elf' })\n      await Species2024Model.insertMany([speciesData])\n      const request = createRequest({ params: { index: 'elf' } })\n      const response = createResponse()\n\n      await SpeciesController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('elf')\n      expect(responseData.name).toBe('Elf')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the species is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await SpeciesController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n\n  describe('showSubspeciesForSpecies', () => {\n    it('returns subspecies matching the species url', async () => {\n      const species = speciesFactory.build({ index: 'elf', name: 'Elf' })\n      const matchingSubspecies = subspeciesFactory.buildList(2, {\n        species: { index: 'elf', name: 'Elf', url: '/api/2024/species/elf' }\n      })\n      const otherSubspecies = subspeciesFactory.build({\n        species: { index: 'dwarf', name: 'Dwarf', url: '/api/2024/species/dwarf' }\n      })\n\n      await Species2024Model.insertMany([species])\n      await Subspecies2024Model.insertMany([...matchingSubspecies, otherSubspecies])\n\n      const request = createRequest({ params: { index: 'elf' } })\n      const response = createResponse()\n\n      await SpeciesController.showSubspeciesForSpecies(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(2)\n      expect(responseData.results).toHaveLength(2)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns empty list when no matching subspecies exist', async () => {\n      const request = createRequest({ params: { index: 'human' } })\n      const response = createResponse()\n\n      await SpeciesController.showSubspeciesForSpecies(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n    })\n  })\n\n  describe('showTraitsForSpecies', () => {\n    it('returns traits matching the species url', async () => {\n      const matchingTraits = traitFactory.buildList(2, {\n        species: [{ index: 'elf', name: 'Elf', url: '/api/2024/species/elf' }]\n      })\n      const otherTrait = traitFactory.build({\n        species: [{ index: 'dwarf', name: 'Dwarf', url: '/api/2024/species/dwarf' }]\n      })\n\n      await Trait2024Model.insertMany([...matchingTraits, otherTrait])\n\n      const request = createRequest({ params: { index: 'elf' } })\n      const response = createResponse()\n\n      await SpeciesController.showTraitsForSpecies(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(2)\n      expect(responseData.results).toHaveLength(2)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns empty list when no matching traits exist', async () => {\n      const request = createRequest({ params: { index: 'human' } })\n      const response = createResponse()\n\n      await SpeciesController.showTraitsForSpecies(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/subspeciesController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport * as SubspeciesController from '@/controllers/api/2024/subspeciesController'\nimport Subspecies2024Model from '@/models/2024/subspecies'\nimport Trait2024Model from '@/models/2024/trait'\nimport { subspeciesFactory } from '@/tests/factories/2024/subspecies.factory'\nimport { traitFactory } from '@/tests/factories/2024/trait.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\nconst dbUri = generateUniqueDbUri('subspecies2024')\n\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(Subspecies2024Model)\nsetupModelCleanup(Trait2024Model)\n\ndescribe('SubspeciesController (2024)', () => {\n  describe('index', () => {\n    it('returns a list of subspecies', async () => {\n      const subspeciesData = subspeciesFactory.buildList(3)\n      await Subspecies2024Model.insertMany(subspeciesData)\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await SubspeciesController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no subspecies exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await SubspeciesController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single subspecies when found', async () => {\n      const subspeciesData = subspeciesFactory.build({ index: 'high-elf', name: 'High Elf' })\n      await Subspecies2024Model.insertMany([subspeciesData])\n      const request = createRequest({ params: { index: 'high-elf' } })\n      const response = createResponse()\n\n      await SubspeciesController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('high-elf')\n      expect(responseData.name).toBe('High Elf')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the subspecies is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await SubspeciesController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n\n  describe('showTraitsForSubspecies', () => {\n    it('returns traits matching the subspecies url', async () => {\n      const matchingTraits = traitFactory.buildList(2, {\n        subspecies: [\n          { index: 'high-elf', name: 'High Elf', url: '/api/2024/subspecies/high-elf' }\n        ]\n      })\n      const otherTrait = traitFactory.build({\n        subspecies: [\n          { index: 'wood-elf', name: 'Wood Elf', url: '/api/2024/subspecies/wood-elf' }\n        ]\n      })\n\n      await Trait2024Model.insertMany([...matchingTraits, otherTrait])\n\n      const request = createRequest({ params: { index: 'high-elf' } })\n      const response = createResponse()\n\n      await SubspeciesController.showTraitsForSubspecies(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(2)\n      expect(responseData.results).toHaveLength(2)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns empty list when no matching traits exist', async () => {\n      const request = createRequest({ params: { index: 'nonexistent-subspecies' } })\n      const response = createResponse()\n\n      await SubspeciesController.showTraitsForSubspecies(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/traitController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport TraitController from '@/controllers/api/2024/traitController'\nimport Trait2024Model from '@/models/2024/trait'\nimport { traitFactory } from '@/tests/factories/2024/trait.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\nconst dbUri = generateUniqueDbUri('trait2024')\n\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(Trait2024Model)\n\ndescribe('TraitController (2024)', () => {\n  describe('index', () => {\n    it('returns a list of traits', async () => {\n      const traitsData = traitFactory.buildList(3)\n      await Trait2024Model.insertMany(traitsData)\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await TraitController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('returns an empty list when no traits exist', async () => {\n      const request = createRequest({ query: {} })\n      const response = createResponse()\n\n      await TraitController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single trait when found', async () => {\n      const traitData = traitFactory.build({ index: 'darkvision', name: 'Darkvision' })\n      await Trait2024Model.insertMany([traitData])\n      const request = createRequest({ params: { index: 'darkvision' } })\n      const response = createResponse()\n\n      await TraitController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('darkvision')\n      expect(responseData.name).toBe('Darkvision')\n      expect(responseData.description).toEqual(traitData.description)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the trait is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await TraitController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/weaponMasteryPropertyController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport WeaponMasteryPropertyController from '@/controllers/api/2024/weaponMasteryPropertyController'\nimport WeaponMasteryPropertyModel from '@/models/2024/weaponMasteryProperty'\nimport { weaponMasteryPropertyFactory } from '@/tests/factories/2024/weaponMasteryProperty.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\nconst dbUri = generateUniqueDbUri('weaponmasteryproperty')\n\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(WeaponMasteryPropertyModel)\n\ndescribe('WeaponMasteryPropertyController', () => {\n  describe('index', () => {\n    it('returns a list of weapon mastery properties', async () => {\n      const weaponMasteryPropertiesData = weaponMasteryPropertyFactory.buildList(3)\n      await WeaponMasteryPropertyModel.insertMany(weaponMasteryPropertiesData)\n\n      const request = createRequest()\n      const response = createResponse()\n\n      await WeaponMasteryPropertyController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results.length).toBe(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({\n            index: weaponMasteryPropertiesData[0].index,\n            name: weaponMasteryPropertiesData[0].name\n          }),\n          expect.objectContaining({\n            index: weaponMasteryPropertiesData[1].index,\n            name: weaponMasteryPropertiesData[1].name\n          }),\n          expect.objectContaining({\n            index: weaponMasteryPropertiesData[2].index,\n            name: weaponMasteryPropertiesData[2].name\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single weapon mastery property when found', async () => {\n      const weaponMasteryPropertyData = weaponMasteryPropertyFactory.build({\n        index: 'cleave',\n        name: 'Cleave'\n      })\n      await WeaponMasteryPropertyModel.insertMany([weaponMasteryPropertyData])\n\n      const request = createRequest({ params: { index: 'cleave' } })\n      const response = createResponse()\n\n      await WeaponMasteryPropertyController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('cleave')\n      expect(responseData.name).toBe('Cleave')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next with an error if the weapon mastery property is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await WeaponMasteryPropertyController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/2024/weaponPropertyController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport WeaponPropertyController from '@/controllers/api/2024/weaponPropertyController'\nimport WeaponPropertyModel from '@/models/2024/weaponProperty'\nimport { weaponPropertyFactory } from '@/tests/factories/2024/weaponProperty.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\nconst dbUri = generateUniqueDbUri('weaponproperty')\n\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(WeaponPropertyModel)\n\ndescribe('WeaponPropertyController', () => {\n  describe('index', () => {\n    it('returns a list of weapon properties', async () => {\n      const weaponPropertiesData = weaponPropertyFactory.buildList(3)\n      await WeaponPropertyModel.insertMany(weaponPropertiesData)\n\n      const request = createRequest()\n      const response = createResponse()\n\n      await WeaponPropertyController.index(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results.length).toBe(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          expect.objectContaining({\n            index: weaponPropertiesData[0].index,\n            name: weaponPropertiesData[0].name\n          }),\n          expect.objectContaining({\n            index: weaponPropertiesData[1].index,\n            name: weaponPropertiesData[1].name\n          }),\n          expect.objectContaining({\n            index: weaponPropertiesData[2].index,\n            name: weaponPropertiesData[2].name\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single weapon property when found', async () => {\n      const weaponPropertyData = weaponPropertyFactory.build({\n        index: 'versatile',\n        name: 'Versatile'\n      })\n      await WeaponPropertyModel.insertMany([weaponPropertyData])\n\n      const request = createRequest({ params: { index: 'versatile' } })\n      const response = createResponse()\n\n      await WeaponPropertyController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.index).toBe('versatile')\n      expect(responseData.name).toBe('Versatile')\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next with an error if the weapon property is not found', async () => {\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      await WeaponPropertyController.show(request, response, mockNext)\n\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/v2014Controller.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport * as ApiController from '@/controllers/api/v2014Controller'\nimport CollectionModel from '@/models/2014/collection'\nimport { collectionFactory } from '@/tests/factories/2014/collection.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('v2014')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(CollectionModel)\n\ndescribe('v2014 API Controller', () => {\n  describe('index', () => {\n    it('returns the map of available API routes', async () => {\n      // Arrange: Seed data within the test\n      const collectionsData = collectionFactory.buildList(3)\n      await CollectionModel.insertMany(collectionsData)\n\n      const request = createRequest()\n      const response = createResponse()\n      const expectedResponse = collectionsData.reduce(\n        (acc, col) => {\n          acc[col.index] = `/api/2014/${col.index}`\n          return acc\n        },\n        {} as Record<string, string>\n      )\n      await ApiController.index(request, response, mockNext)\n\n      // Assert\n      const actualResponse = JSON.parse(response._getData())\n      expect(response.statusCode).toBe(200)\n      expect(actualResponse).toEqual(expectedResponse)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('handles database errors during find', async () => {\n      // Arrange\n      const request = createRequest()\n      const response = createResponse()\n      const error = new Error('Database find failed')\n      vi.spyOn(CollectionModel, 'find').mockImplementationOnce(() => {\n        const query = {\n          select: vi.fn().mockReturnThis(),\n          sort: vi.fn().mockReturnThis(),\n          exec: vi.fn().mockRejectedValueOnce(error)\n        } as any\n        return query\n      })\n\n      // Act\n      await ApiController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n\n    it('returns an empty object when no collections exist', async () => {\n      // Arrange: Cleanup is handled by setupModelCleanup\n      const request = createRequest()\n      const response = createResponse()\n\n      // Act\n      await ApiController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      expect(JSON.parse(response._getData())).toEqual({})\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/api/v2024Controller.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport * as ApiController from '@/controllers/api/v2024Controller'\nimport CollectionModel from '@/models/2024/collection'\nimport { collectionFactory } from '@/tests/factories/2024/collection.factory'\nimport { mockNext as defaultMockNext } from '@/tests/support'\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('v2024')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(CollectionModel)\n\ndescribe('v2024 API Controller', () => {\n  describe('index', () => {\n    it('returns the map of available API routes', async () => {\n      const collectionsData = collectionFactory.buildList(3)\n      await CollectionModel.insertMany(collectionsData)\n\n      const request = createRequest()\n      const response = createResponse()\n      const expectedResponse = collectionsData.reduce(\n        (acc, col) => {\n          acc[col.index] = `/api/2024/${col.index}`\n          return acc\n        },\n        {} as Record<string, string>\n      )\n      await ApiController.index(request, response, mockNext)\n\n      // Assert: Check the response\n      const actualResponse = JSON.parse(response._getData())\n      expect(response.statusCode).toBe(200)\n      expect(actualResponse).toEqual(expectedResponse)\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('handles database errors during find', async () => {\n      // Arrange\n      const request = createRequest()\n      const response = createResponse()\n      const error = new Error('Database find failed')\n      vi.spyOn(CollectionModel, 'find').mockImplementationOnce(() => {\n        const query = {\n          select: vi.fn().mockReturnThis(),\n          sort: vi.fn().mockReturnThis(),\n          exec: vi.fn().mockRejectedValueOnce(error)\n        } as any\n        return query\n      })\n\n      // Act\n      await ApiController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n\n    it('returns an empty object when no collections exist', async () => {\n      // Arrange: Cleanup is handled by setupModelCleanup\n      const request = createRequest()\n      const response = createResponse()\n      await ApiController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      expect(JSON.parse(response._getData())).toEqual({})\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/apiController.test.ts",
    "content": "import { createRequest, createResponse } from 'node-mocks-http'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport deprecatedApiController from '@/controllers/apiController'\nimport CollectionModel from '@/models/2014/collection' // Use Model suffix convention\nimport { mockNext as defaultMockNext } from '@/tests/support' // Assuming mockNext is here\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('api')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(CollectionModel)\n\ndescribe('deprecated /api controller', () => {\n  it('redirects /api/ to /api/2014/', async () => {\n    const request = createRequest({ path: '/' }) // Path relative to where the controller is mounted\n    const response = createResponse()\n    const redirectSpy = vi.spyOn(response, 'redirect')\n\n    await deprecatedApiController(request, response, mockNext)\n\n    expect(response.statusCode).toBe(301)\n    expect(redirectSpy).toHaveBeenCalledWith(301, '/api/2014/')\n    expect(mockNext).not.toHaveBeenCalled()\n  })\n\n  it('redirects /api/<valid-endpoint> to /api/2014/<valid-endpoint>', async () => {\n    await CollectionModel.insertMany([{ index: 'valid-endpoint' }])\n    const request = createRequest({ method: 'GET', path: '/valid-endpoint' })\n    const response = createResponse()\n    const redirectSpy = vi.spyOn(response, 'redirect')\n\n    await deprecatedApiController(request, response, mockNext)\n\n    expect(response.statusCode).toBe(301)\n    expect(redirectSpy).toHaveBeenCalledWith(301, '/api/2014/valid-endpoint')\n    expect(mockNext).not.toHaveBeenCalled()\n  })\n\n  it('responds with 404 for invalid sub-routes', async () => {\n    const request = createRequest({ path: '/invalid-endpoint' })\n    const response = createResponse()\n    const sendStatusSpy = vi.spyOn(response, 'sendStatus')\n\n    await deprecatedApiController(request, response, mockNext)\n\n    expect(sendStatusSpy).toHaveBeenCalledWith(404)\n    expect(mockNext).not.toHaveBeenCalled()\n  })\n})\n"
  },
  {
    "path": "src/tests/controllers/globalSetup.ts",
    "content": "import { MongoMemoryServer } from 'mongodb-memory-server'\n\n// Define types for global variables\n// Use declare global {} for augmenting the global scope safely\ndeclare global {\n  var __MONGOD__: MongoMemoryServer | undefined\n}\n\n// Function to generate a temporary directory path for Redis\n// Removed getTmpDir as it's no longer needed\n\nexport async function setup(): Promise<() => Promise<void>> {\n  console.log('\\n[Global Setup - Unit Tests] Starting test servers...')\n\n  // --- MongoDB Setup ---\n  try {\n    console.log('[Global Setup - Unit Tests] Starting MongoMemoryServer...')\n    const mongod = await MongoMemoryServer.create()\n    const baseMongoUri = mongod.getUri().split('?')[0]\n    const serverUri = baseMongoUri.endsWith('/') ? baseMongoUri : baseMongoUri + '/'\n\n    process.env.TEST_MONGODB_URI_BASE = serverUri // Set env var for tests\n    globalThis.__MONGOD__ = mongod // Store instance globally\n    console.log(`[Global Setup - Unit Tests] MongoMemoryServer started at base URI: ${serverUri}`)\n  } catch (error) {\n    console.error('[Global Setup - Unit Tests] Failed to start MongoMemoryServer:', error)\n    throw new Error(\n      `Failed to start MongoMemoryServer: ${error instanceof Error ? error.message : error}`,\n      { cause: error }\n    )\n  }\n\n  console.log('[Global Setup - Unit Tests] MongoMemoryServer running.')\n\n  // Return the teardown function\n  return async () => {\n    console.log('\\n[Global Teardown - Unit Tests] Stopping test servers...')\n    if (globalThis.__MONGOD__) {\n      try {\n        await globalThis.__MONGOD__.stop()\n        console.log('[Global Teardown - Unit Tests] MongoMemoryServer stopped.')\n      } catch (error) {\n        console.error('[Global Teardown - Unit Tests] Error stopping MongoMemoryServer:', error)\n      }\n      globalThis.__MONGOD__ = undefined\n    }\n    console.log('[Global Teardown - Unit Tests] MongoMemoryServer stopped.')\n  }\n}\n"
  },
  {
    "path": "src/tests/controllers/simpleController.test.ts",
    "content": "import { type Model } from 'mongoose'\nimport { createRequest, createResponse } from 'node-mocks-http'\nimport { beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport SimpleController from '@/controllers/simpleController'\nimport AbilityScoreModel from '@/models/2014/abilityScore' // Use Model suffix convention\nimport { abilityScoreFactory } from '@/tests/factories/2014/abilityScore.factory' // Import factory\nimport { mockNext as defaultMockNext } from '@/tests/support' // Assuming mockNext is here\nimport {\n  generateUniqueDbUri,\n  setupIsolatedDatabase,\n  setupModelCleanup,\n  teardownIsolatedDatabase\n} from '@/tests/support/db'\n\nconst mockNext = vi.fn(defaultMockNext)\n\n// Generate URI for this test file\nconst dbUri = generateUniqueDbUri('simple')\n\n// Setup hooks using helpers\nsetupIsolatedDatabase(dbUri)\nteardownIsolatedDatabase()\nsetupModelCleanup(AbilityScoreModel)\n\nlet abilityScoreController: SimpleController // Keep declaration\n\ndescribe('SimpleController (with AbilityScore)', () => {\n  beforeAll(async () => {\n    // Initialize controller after connection\n    abilityScoreController = new SimpleController(AbilityScoreModel as Model<any>)\n  })\n\n  describe('index', () => {\n    it('returns a list of documents', async () => {\n      // Arrange\n      const scoresData = abilityScoreFactory.buildList(3)\n      await AbilityScoreModel.insertMany(scoresData)\n      const request = createRequest()\n      const response = createResponse()\n\n      // Act\n      await abilityScoreController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(3)\n      expect(responseData.results).toHaveLength(3)\n      expect(responseData.results).toEqual(\n        expect.arrayContaining([\n          // SimpleController index returns index, name, url\n          expect.objectContaining({\n            index: scoresData[0].index,\n            name: scoresData[0].name,\n            url: scoresData[0].url\n          }),\n          expect.objectContaining({\n            index: scoresData[1].index,\n            name: scoresData[1].name,\n            url: scoresData[1].url\n          }),\n          expect.objectContaining({\n            index: scoresData[2].index,\n            name: scoresData[2].name,\n            url: scoresData[2].url\n          })\n        ])\n      )\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('handles database errors during find', async () => {\n      // Arrange\n      const request = createRequest()\n      const response = createResponse()\n      const error = new Error('Database find failed')\n      vi.spyOn(AbilityScoreModel, 'find').mockImplementationOnce(() => {\n        const query = {\n          select: vi.fn().mockReturnThis(),\n          sort: vi.fn().mockReturnThis(),\n          exec: vi.fn().mockRejectedValueOnce(error)\n        } as any\n        return query\n      })\n\n      // Act\n      await abilityScoreController.index(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n\n    it('returns an empty list when no documents exist', async () => {\n      const request = createRequest()\n      const response = createResponse()\n      await abilityScoreController.index(request, response, mockNext)\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      expect(responseData.count).toBe(0)\n      expect(responseData.results).toEqual([])\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('show', () => {\n    it('returns a single document when found', async () => {\n      // Arrange\n      const scoreData = abilityScoreFactory.build({ index: 'str', name: 'STR' })\n      await AbilityScoreModel.insertMany([scoreData])\n      const request = createRequest({ params: { index: 'str' } })\n      const response = createResponse()\n\n      // Act\n      await abilityScoreController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      const responseData = JSON.parse(response._getData())\n      // Check against the specific fields returned by show\n      expect(responseData).toMatchObject({\n        index: scoreData.index,\n        name: scoreData.name,\n        full_name: scoreData.full_name,\n        desc: scoreData.desc\n        // url is often included too\n      })\n      expect(mockNext).not.toHaveBeenCalled()\n    })\n\n    it('calls next() when the document is not found', async () => {\n      // Arrange\n      const request = createRequest({ params: { index: 'nonexistent' } })\n      const response = createResponse()\n\n      // Act\n      await abilityScoreController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith()\n    })\n\n    it('handles database errors during findOne', async () => {\n      // Arrange\n      const request = createRequest({ params: { index: 'str' } })\n      const response = createResponse()\n      const error = new Error('Database findOne failed')\n      vi.spyOn(AbilityScoreModel, 'findOne').mockRejectedValueOnce(error)\n\n      // Act\n      await abilityScoreController.show(request, response, mockNext)\n\n      // Assert\n      expect(response.statusCode).toBe(200)\n      expect(response._getData()).toBe('')\n      expect(mockNext).toHaveBeenCalledOnce()\n      expect(mockNext).toHaveBeenCalledWith(error)\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/factories/2014/abilityScore.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { AbilityScore } from '@/models/2014/abilityScore'\n\nimport { apiReferenceFactory } from './common.factory'\n\n// Define the factory using fishery\nexport const abilityScoreFactory = Factory.define<AbilityScore>(\n  ({ sequence, params, transientParams }) => {\n    // params are overrides\n    // transientParams are params not part of the final object, useful for intermediate logic\n    // sequence provides a unique number for each generated object\n\n    // Use transientParams for defaults that might be complex or used multiple times\n    const name = params.name ?? transientParams.baseName ?? `Ability Score ${sequence}`\n    const index = params.index ?? name.toLowerCase().replace(/\\s+/g, '-')\n\n    return {\n      // Required fields\n      index,\n      name,\n      full_name: params.full_name ?? `Full ${name}`,\n      desc: params.desc ?? [faker.lorem.paragraph()], // Simplified default\n      url: params.url ?? `/api/ability-scores/${index}`,\n      updated_at: params.updated_at ?? faker.date.recent().toISOString(),\n\n      // Non-required fields - Use the imported factory\n      skills: params.skills ?? apiReferenceFactory.buildList(2), // Build a list of 2 APIReferences\n\n      // Merging params ensures overrides work correctly\n      ...params\n    }\n  }\n)\n"
  },
  {
    "path": "src/tests/factories/2014/alignment.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Alignment } from '@/models/2014/alignment'\n\nexport const alignmentFactory = Factory.define<Alignment>(({ sequence, params }) => {\n  const name = params.name ?? `Alignment ${sequence}`\n  const index = params.index ?? name.toLowerCase().replace(/\\s+/g, '-')\n\n  return {\n    index,\n    name,\n    abbreviation: params.abbreviation ?? name.substring(0, 2).toUpperCase(), // Simple default\n    desc: params.desc ?? faker.lorem.sentence(),\n    url: params.url ?? `/api/alignments/${index}`,\n    updated_at: params.updated_at ?? faker.date.recent().toISOString(),\n    ...params // Ensure overrides from params are applied\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2014/background.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Background, EquipmentRef } from '@/models/2014/background'\n\nimport { apiReferenceFactory, choiceFactory } from './common.factory'\n\n// EquipmentRef factory\nconst equipmentRefFactory = Factory.define<EquipmentRef>(() => ({\n  equipment: apiReferenceFactory.build(),\n  quantity: faker.number.int({ min: 1, max: 5 })\n}))\n\n// Feature factory\n// Cannot name this Feature due to conflict with built-in Feature type\nconst backgroundFeatureFactory = Factory.define<Background['feature']>(() => ({\n  name: faker.lorem.words(3),\n  desc: [faker.lorem.paragraph()]\n}))\n\n// Main Background factory - USING COMMON FACTORIES\nexport const backgroundFactory = Factory.define<Background>(({ sequence, params }) => {\n  const name = params.name ?? `Background ${sequence}`\n  const index = params.index ?? name.toLowerCase().replace(/\\s+/g, '-')\n\n  // Return object with defaults. Complex overrides require manual construction\n  // or passing deep overrides to .build()\n  return {\n    index: index,\n    name: name,\n    starting_proficiencies: apiReferenceFactory.buildList(2),\n    language_options: choiceFactory.build(),\n    url: `/api/backgrounds/${index}`,\n    starting_equipment: equipmentRefFactory.buildList(1),\n    starting_equipment_options: choiceFactory.buildList(1),\n    feature: backgroundFeatureFactory.build(),\n    personality_traits: choiceFactory.build(),\n    ideals: choiceFactory.build(),\n    bonds: choiceFactory.build(),\n    flaws: choiceFactory.build(),\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2014/class.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { apiReferenceFactory, choiceFactory, createIndex, createUrl } from './common.factory'\n\nimport type {\n  Class,\n  ClassEquipment,\n  MultiClassing,\n  MultiClassingPrereq,\n  Spellcasting,\n  SpellcastingInfo\n} from '@/models/2014/class'\n\n// --- Nested Factories --- //\n\nconst equipmentFactory = Factory.define<ClassEquipment>(({ params }) => {\n  const builtEquipmentRef = apiReferenceFactory.build(params.equipment)\n  return {\n    equipment: {\n      index: builtEquipmentRef.index,\n      name: builtEquipmentRef.name,\n      url: builtEquipmentRef.url\n    },\n    quantity: params.quantity ?? faker.number.int({ min: 1, max: 5 })\n  }\n})\n\nconst spellcastingInfoFactory = Factory.define<SpellcastingInfo>(({ params }) => ({\n  desc: params.desc ?? [faker.lorem.sentence()],\n  name: params.name ?? faker.lorem.words(3)\n}))\n\nconst spellcastingFactory = Factory.define<Spellcasting>(({ params }) => {\n  const builtInfo = spellcastingInfoFactory.buildList(params.info?.length ?? 1, params.info?.[0]) // Build at least one info\n  const builtAbility = apiReferenceFactory.build(params.spellcasting_ability)\n  return {\n    info: builtInfo.map((i) => ({ desc: i.desc, name: i.name })), // Map to ensure correct structure\n    level: params.level ?? faker.number.int({ min: 1, max: 5 }),\n    spellcasting_ability: {\n      index: builtAbility.index,\n      name: builtAbility.name,\n      url: builtAbility.url\n    }\n  }\n})\n\nconst multiClassingPrereqFactory = Factory.define<MultiClassingPrereq>(({ params }) => {\n  const builtAbility = apiReferenceFactory.build(params.ability_score)\n  return {\n    ability_score: { index: builtAbility.index, name: builtAbility.name, url: builtAbility.url },\n    minimum_score: params.minimum_score ?? faker.helpers.arrayElement([10, 12, 13, 14, 15])\n  }\n})\n\nconst multiClassingFactory = Factory.define<MultiClassing>(({ params }) => {\n  // Build optional parts explicitly\n  const builtPrereqs =\n    params.prerequisites !== null\n      ? (params.prerequisites ??\n        multiClassingPrereqFactory.buildList(faker.number.int({ min: 0, max: 1 })))\n      : undefined\n  const builtPrereqOpts =\n    params.prerequisite_options !== null\n      ? (params.prerequisite_options ??\n        (faker.datatype.boolean(0.1) ? choiceFactory.build() : undefined))\n      : undefined\n  const builtProfs =\n    params.proficiencies !== null\n      ? (params.proficiencies ??\n        apiReferenceFactory.buildList(faker.number.int({ min: 0, max: 2 })))\n      : undefined\n  const builtProfChoices =\n    params.proficiency_choices !== null\n      ? (params.proficiency_choices ??\n        (faker.datatype.boolean(0.2)\n          ? choiceFactory.buildList(faker.number.int({ min: 1, max: 2 }))\n          : undefined))\n      : undefined\n\n  return {\n    prerequisites: builtPrereqs?.map((p) => ({\n      ability_score: p.ability_score,\n      minimum_score: p.minimum_score\n    })), // Map to ensure structure\n    prerequisite_options: builtPrereqOpts ? choiceFactory.build(builtPrereqOpts) : undefined, // Re-build choice if needed\n    proficiencies: builtProfs?.map((p) => ({ index: p.index, name: p.name, url: p.url })), // Map API Refs\n    proficiency_choices: builtProfChoices?.map((c) => choiceFactory.build(c)) // Re-build choices if needed\n  }\n})\n\n// --- Class Factory (Main) --- //\n\nexport const classFactory = Factory.define<Omit<Class, '_id' | 'collectionName'>>(\n  ({ sequence, params }) => {\n    const name = params.name ?? `Class ${sequence}`\n    const index = params.index ?? createIndex(name)\n    const url = params.url ?? createUrl('classes', index)\n\n    // Build dependencies\n    const builtProfs =\n      params.proficiencies ?? apiReferenceFactory.buildList(faker.number.int({ min: 2, max: 4 }))\n    const builtProfChoices =\n      params.proficiency_choices ?? choiceFactory.buildList(faker.number.int({ min: 1, max: 2 }))\n    const builtSavingThrows = params.saving_throws ?? apiReferenceFactory.buildList(2)\n    const builtStartingEquip =\n      params.starting_equipment ?? equipmentFactory.buildList(faker.number.int({ min: 1, max: 3 }))\n    const builtStartingEquipOpts =\n      params.starting_equipment_options ??\n      choiceFactory.buildList(faker.number.int({ min: 1, max: 2 }))\n    const builtSubclasses =\n      params.subclasses ?? apiReferenceFactory.buildList(faker.number.int({ min: 1, max: 3 }))\n\n    // Build optional/complex parts\n    const builtMultiClassing = multiClassingFactory.build(params.multi_classing) // Always build this, pass null via params to omit parts\n    const builtSpellcasting =\n      params.spellcasting === null\n        ? undefined\n        : (params.spellcasting ??\n          (faker.datatype.boolean(0.5) ? spellcastingFactory.build() : undefined))\n\n    return {\n      index,\n      name,\n      url,\n      hit_die: params.hit_die ?? faker.helpers.arrayElement([6, 8, 10, 12]),\n      class_levels: params.class_levels ?? `/api/classes/${index}/levels`, // Default URL structure\n      multi_classing: builtMultiClassing, // Use the fully built object\n      proficiencies: builtProfs.map((p) => ({ index: p.index, name: p.name, url: p.url })), // Map API Refs\n      proficiency_choices: builtProfChoices.map((c) => choiceFactory.build(c)), // Re-build choices\n      saving_throws: builtSavingThrows.map((p) => ({ index: p.index, name: p.name, url: p.url })), // Map API Refs\n      spellcasting: builtSpellcasting ? spellcastingFactory.build(builtSpellcasting) : undefined, // Re-build if exists\n      spells: params.spells ?? `/api/classes/${index}/spells`, // Default URL structure\n      starting_equipment: builtStartingEquip.map((e) => equipmentFactory.build(e)), // Re-build equipment\n      starting_equipment_options: builtStartingEquipOpts.map((c) => choiceFactory.build(c)), // Re-build choices\n      subclasses: builtSubclasses.map((s) => ({ index: s.index, name: s.name, url: s.url })), // Map API Refs\n      updated_at: params.updated_at ?? faker.date.past().toISOString()\n    }\n  }\n)\n"
  },
  {
    "path": "src/tests/factories/2014/collection.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { createIndex } from './common.factory'\n\nimport type { Collection } from '@/models/2014/collection'\n\n// Factory only needs to define properties present in the Collection model\nexport const collectionFactory = Factory.define<Omit<Collection, '_id' | 'collectionName'>>(\n  ({ sequence, params }) => {\n    // Generate a plausible index, or use one provided\n    const index = params.index ?? createIndex(`${faker.word.noun()} ${sequence}`)\n\n    return {\n      index\n    }\n  }\n)\n"
  },
  {
    "path": "src/tests/factories/2014/common.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { AreaOfEffect } from '@/models/common/areaOfEffect'\nimport { Choice, OptionsArrayOptionSet, StringOption } from '@/models/common/choice'\nimport { Damage } from '@/models/common/damage'\nimport { DifficultyClass } from '@/models/common/difficultyClass'\n\n// --- Helper Functions ---\nexport const createIndex = (name: string): string =>\n  name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n\nexport const createUrl = (resource: string, index: string): string => `/api/${resource}/${index}`\n\n// --- APIReference ---\nexport const apiReferenceFactory = Factory.define<APIReference>(({ sequence, params }) => {\n  const name = params?.name ?? `Reference ${sequence}`\n  const index = params?.index ?? createIndex(name)\n  // Default to a generic 'testing' resource if not provided\n  const resource = params?.url?.split('/')[2] ?? 'testing'\n  return {\n    index: index,\n    name: name,\n    url: params?.url ?? createUrl(resource, index)\n  }\n})\n\n// --- AreaOfEffect ---\nexport const areaOfEffectFactory = Factory.define<AreaOfEffect>(() => ({\n  size: faker.number.int({ min: 5, max: 30 }),\n  type: faker.helpers.arrayElement(['sphere', 'cube', 'cylinder', 'line', 'cone'])\n}))\n\n// --- DifficultyClass ---\nexport const difficultyClassFactory = Factory.define<DifficultyClass>(() => ({\n  dc_type: apiReferenceFactory.build(),\n  dc_value: faker.number.int({ min: 10, max: 25 }),\n  success_type: faker.helpers.arrayElement(['none', 'half', 'other'])\n}))\n\n// --- Damage ---\nexport const damageFactory = Factory.define<Damage>(() => ({\n  damage_type: apiReferenceFactory.build(),\n  damage_dice: `${faker.number.int({ min: 1, max: 4 })}d${faker.helpers.arrayElement([\n    4, 6, 8, 10, 12\n  ])}`\n}))\n\n// --- Option (using StringOption as a simple representative) ---\n// Tests needing specific option types will need dedicated factories or manual construction\nexport const stringOptionFactory = Factory.define<StringOption>(({ sequence }) => ({\n  option_type: 'string',\n  string: `Option String ${sequence}`\n}))\n\n// --- OptionSet (using OptionsArrayOptionSet as representative) ---\n// Tests needing specific option set types will need dedicated factories or manual construction\nexport const optionsArrayOptionSetFactory = Factory.define<OptionsArrayOptionSet>(() => ({\n  option_set_type: 'options_array',\n  options: stringOptionFactory.buildList(1) // Default with one simple string option\n}))\n\n// --- Choice (Simplified) ---\n// This now uses the more concrete optionsArrayOptionSetFactory\nexport const choiceFactory = Factory.define<Choice>(() => ({\n  desc: faker.lorem.sentence(),\n  choose: 1,\n  type: 'equipment', // Default type\n  from: optionsArrayOptionSetFactory.build() // Use the concrete subtype factory\n}))\n\n// --- Other Option Subtypes (Placeholders - build as needed) ---\n// export const referenceOptionFactory = Factory.define<ReferenceOption>(...)\n// export const actionOptionFactory = Factory.define<ActionOption>(...)\n// export const multipleOptionFactory = Factory.define<MultipleOption>(...)\n// export const idealOptionFactory = Factory.define<IdealOption>(...)\n// export const countedReferenceOptionFactory = Factory.define<CountedReferenceOption>(...)\n// ... etc.\n"
  },
  {
    "path": "src/tests/factories/2014/condition.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Condition } from '@/models/2014/condition'\n\nexport const conditionFactory = Factory.define<Condition>(({ sequence }) => {\n  const name = `Condition ${sequence} - ${faker.lorem.words(2)}`\n  const index = name.toLowerCase().replace(/\\s+/g, '-')\n\n  return {\n    index: index,\n    name: name,\n    desc: [faker.lorem.paragraph(), faker.lorem.paragraph()],\n    url: `/api/conditions/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2014/damageType.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { DamageType } from '@/models/2014/damageType'\n\nexport const damageTypeFactory = Factory.define<DamageType>(({ sequence }) => {\n  const name = `Damage Type ${sequence} - ${faker.lorem.word()}`\n  const index = name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n\n  return {\n    index: index,\n    name: name,\n    desc: [faker.lorem.paragraph()],\n    url: `/api/damage-types/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2014/equipment.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport {\n  ArmorClass,\n  Content,\n  Cost,\n  Equipment,\n  Range,\n  Speed,\n  ThrowRange\n} from '@/models/2014/equipment'\nimport { Damage } from '@/models/common/damage'\n\nimport { apiReferenceFactory } from './common.factory'\n\n// --- Sub-factories (Simple defaults/placeholders) ---\n\nconst armorClassFactory = Factory.define<ArmorClass>(() => ({\n  base: faker.number.int({ min: 10, max: 18 }),\n  dex_bonus: faker.datatype.boolean(),\n  max_bonus: undefined // Usually optional\n}))\n\nconst contentFactory = Factory.define<Content>(() => ({\n  item: apiReferenceFactory.build(),\n  quantity: faker.number.int({ min: 1, max: 10 })\n}))\n\nconst costFactory = Factory.define<Cost>(() => ({\n  quantity: faker.number.int({ min: 1, max: 1000 }),\n  unit: faker.helpers.arrayElement(['cp', 'sp', 'gp'])\n}))\n\nconst damageFactory = Factory.define<Damage>(() => ({\n  damage_dice: `${faker.number.int({ min: 1, max: 2 })}d${faker.helpers.arrayElement([\n    4, 6, 8, 10, 12\n  ])}`,\n  damage_type: apiReferenceFactory.build() // Link to a damage type\n}))\n\nconst rangeFactory = Factory.define<Range>(() => ({\n  normal: faker.number.int({ min: 5, max: 100 }),\n  long: undefined // Optional\n}))\n\nconst speedFactory = Factory.define<Speed>(() => ({\n  quantity: faker.number.int({ min: 20, max: 60 }),\n  unit: 'ft/round'\n}))\n\nconst throwRangeFactory = Factory.define<ThrowRange>(() => ({\n  normal: faker.number.int({ min: 5, max: 30 }),\n  long: faker.number.int({ min: 31, max: 120 })\n}))\n\nconst twoHandedDamageFactory = Factory.define<Damage>(() => ({\n  damage_dice: `${faker.number.int({ min: 1, max: 2 })}d${faker.helpers.arrayElement([\n    6, 8, 10, 12\n  ])}`,\n  damage_type: apiReferenceFactory.build()\n}))\n\n// --- Main Equipment Factory ---\nexport const equipmentFactory = Factory.define<Equipment>(({ sequence, params }) => {\n  const name = `Equipment ${sequence} - ${faker.commerce.productName()}`\n  const index = name.toLowerCase().replace(/\\s+/g, '-')\n\n  return {\n    index: index,\n    name: name,\n    desc: [faker.lorem.sentence()],\n    cost: costFactory.build(),\n    equipment_category: apiReferenceFactory.build(), // Default category\n    url: `/api/equipment/${index}`,\n    updated_at: faker.date.recent().toISOString(),\n\n    // Most fields are optional and depend on the equipment type.\n    // Defaulting to undefined. Tests must build specific types.\n    armor_category: undefined,\n    armor_class: armorClassFactory.build(),\n    capacity: undefined,\n    category_range: undefined,\n    contents: contentFactory.buildList(1),\n    damage: damageFactory.build(),\n    gear_category: undefined,\n    image: params.image ?? `/images/equipment/${index}.png`,\n    properties: apiReferenceFactory.buildList(faker.number.int({ min: 0, max: 3 })),\n    quantity: undefined,\n    range: rangeFactory.build(),\n    special: undefined,\n    speed: speedFactory.build(),\n    stealth_disadvantage: undefined,\n    str_minimum: undefined,\n    throw_range: throwRangeFactory.build(),\n    tool_category: undefined,\n    two_handed_damage: twoHandedDamageFactory.build(),\n    vehicle_category: undefined,\n    weapon_category: undefined,\n    weapon_range: undefined,\n    weight: undefined\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2014/equipmentCategory.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { EquipmentCategory } from '@/models/2014/equipmentCategory'\n\nimport { apiReferenceFactory } from './common.factory' // Import common factory\n\nexport const equipmentCategoryFactory = Factory.define<EquipmentCategory>(({ sequence }) => {\n  const name = `Equipment Category ${sequence} - ${faker.lorem.words(2)}`\n  const index = name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n\n  return {\n    index: index,\n    name: name,\n    equipment: apiReferenceFactory.buildList(faker.number.int({ min: 1, max: 5 })), // Build a list of equipment references\n    url: `/api/equipment-categories/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2014/feat.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { apiReferenceFactory, createIndex, createUrl } from './common.factory' // Import common factory for APIReference and choiceFactory\n\nimport type { Feat, Prerequisite } from '@/models/2014/feat'\n\n// --- Prerequisite Factory ---\nconst prerequisiteFactory = Factory.define<Prerequisite>(({ params }) => {\n  // Build dependency first\n  const builtApiRef = apiReferenceFactory.build(params.ability_score)\n  return {\n    // Explicitly construct the object\n    ability_score: {\n      index: builtApiRef.index,\n      name: builtApiRef.name,\n      url: builtApiRef.url\n    },\n    minimum_score: params.minimum_score ?? faker.number.int({ min: 8, max: 15 })\n  }\n})\n\n// --- Feat Factory ---\nexport const featFactory = Factory.define<Omit<Feat, '_id' | 'collectionName'>>(\n  ({ sequence, params }) => {\n    const name = params.name ?? `${faker.word.adjective()} Feat ${sequence}`\n    const index = params.index ?? createIndex(name)\n\n    // Explicitly build a list of Prerequisite objects\n    const prerequisites =\n      params.prerequisites ?? prerequisiteFactory.buildList(faker.number.int({ min: 0, max: 1 }))\n\n    return {\n      index,\n      name,\n      prerequisites: prerequisites.map((p) => ({\n        // Ensure the array type is correct\n        ability_score: p.ability_score,\n        minimum_score: p.minimum_score\n      })),\n      desc: params.desc ?? [faker.lorem.paragraph()],\n      url: params.url ?? createUrl('feats', index),\n      updated_at: params.updated_at ?? faker.date.past().toISOString()\n    }\n  }\n)\n"
  },
  {
    "path": "src/tests/factories/2014/feature.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport {\n  Feature,\n  FeaturePrerequisite,\n  FeatureSpecific,\n  LevelPrerequisite,\n  SpellPrerequisite\n} from '@/models/2014/feature'\n\nimport { apiReferenceFactory, choiceFactory } from './common.factory'\n\n// --- Sub-factories ---\n\nconst levelPrerequisiteFactory = Factory.define<LevelPrerequisite>(() => ({\n  type: 'level',\n  level: faker.number.int({ min: 1, max: 20 })\n}))\n\nconst featurePrerequisiteFactory = Factory.define<FeaturePrerequisite>(() => ({\n  type: 'feature',\n  feature: `/api/features/${faker.lorem.slug()}` // Example feature URL\n}))\n\nconst spellPrerequisiteFactory = Factory.define<SpellPrerequisite>(() => ({\n  type: 'spell',\n  spell: `/api/spells/${faker.lorem.slug()}` // Example spell URL\n}))\n\n// Basic placeholder for FeatureSpecific - tests needing details should build manually\nconst featureSpecificFactory = Factory.define<FeatureSpecific>(() => ({\n  subfeature_options: choiceFactory.build(),\n  expertise_options: choiceFactory.build(),\n  invocations: apiReferenceFactory.buildList(1)\n}))\n\n// --- Main Feature Factory ---\nexport const featureFactory = Factory.define<Feature>(({ sequence }) => {\n  const name = `Feature ${sequence} - ${faker.lorem.words(2)}`\n  const index = name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n  const level = faker.number.int({ min: 1, max: 20 })\n\n  return {\n    index: index,\n    name: name,\n    level: level,\n    class: apiReferenceFactory.build(), // Link to a class by default\n    desc: [faker.lorem.paragraph()],\n    url: `/api/features/${index}`,\n    updated_at: faker.date.recent().toISOString(),\n\n    // Optional fields defaulted to undefined\n    prerequisites: [\n      levelPrerequisiteFactory.build(),\n      featurePrerequisiteFactory.build(),\n      spellPrerequisiteFactory.build()\n    ], // Default to no prerequisites\n    parent: undefined,\n    subclass: undefined,\n    feature_specific: featureSpecificFactory.build(),\n    reference: undefined\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2014/language.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Language } from '@/models/2014/language'\n\nconst languageTypes = ['Standard', 'Exotic']\nconst scripts = ['Common', 'Elvish', 'Dwarvish', 'Infernal', 'Draconic', 'Celestial']\n\nexport const languageFactory = Factory.define<Language>(({ sequence }) => {\n  const name = `Language ${sequence} - ${faker.lorem.word()}`\n  const index = name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n\n  return {\n    index: index,\n    name: name,\n    desc: faker.lorem.sentence(),\n    script: faker.helpers.arrayElement(scripts),\n    type: faker.helpers.arrayElement(languageTypes),\n    typical_speakers: [faker.person.jobTitle(), faker.person.jobTitle()],\n    url: `/api/languages/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2014/level.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { apiReferenceFactory } from './common.factory'\n\nimport type {\n  ClassSpecific,\n  ClassSpecificCreatingSpellSlot,\n  ClassSpecificMartialArt,\n  ClassSpecificSneakAttack,\n  Level,\n  LevelSpellcasting,\n  SubclassSpecific\n} from '@/models/2014/level'\n\nconst createIndex = (base: string, level: number): string => `${base}-${level}`\nconst createUrl = (resource: string, index: string): string => `/api/${resource}/${index}`\n\n// --- Nested Factories for Level --- //\n\nconst classSpecificCreatingSpellSlotFactory = Factory.define<ClassSpecificCreatingSpellSlot>(\n  () => ({\n    sorcery_point_cost: faker.number.int({ min: 1, max: 5 }),\n    spell_slot_level: faker.number.int({ min: 1, max: 3 })\n  })\n)\n\nconst classSpecificMartialArtFactory = Factory.define<ClassSpecificMartialArt>(() => ({\n  dice_count: 1,\n  dice_value: faker.helpers.arrayElement([4, 6])\n}))\n\nconst classSpecificSneakAttackFactory = Factory.define<ClassSpecificSneakAttack>(() => ({\n  dice_count: faker.number.int({ min: 1, max: 4 }),\n  dice_value: 6\n}))\n\n// Factory for the main ClassSpecific object (many optional fields)\nconst classSpecificFactory = Factory.define<ClassSpecific>(() => {\n  // Only include a few common ones by default, others can be added via params\n  const specifics: Partial<ClassSpecific> = {}\n  if (faker.datatype.boolean(0.2)) specifics.extra_attacks = 1\n  if (faker.datatype.boolean(0.1)) specifics.ki_points = faker.number.int({ min: 2, max: 10 })\n  if (faker.datatype.boolean(0.1)) specifics.sorcery_points = faker.number.int({ min: 2, max: 10 })\n  if (faker.datatype.boolean(0.1)) specifics.rage_count = faker.number.int({ min: 2, max: 4 })\n  if (faker.datatype.boolean(0.1))\n    specifics.invocations_known = faker.number.int({ min: 2, max: 5 })\n  // Add others here as needed or pass through params\n  return specifics\n})\n\n// Factory for LevelSpellcasting (many required fields)\nconst levelSpellcastingFactory = Factory.define<LevelSpellcasting>(({ params }) => ({\n  cantrips_known: params.cantrips_known ?? faker.number.int({ min: 2, max: 4 }),\n  spell_slots_level_1: params.spell_slots_level_1 ?? faker.number.int({ min: 0, max: 4 }),\n  spell_slots_level_2: params.spell_slots_level_2 ?? faker.number.int({ min: 0, max: 3 }),\n  spell_slots_level_3: params.spell_slots_level_3 ?? faker.number.int({ min: 0, max: 3 }),\n  spell_slots_level_4: params.spell_slots_level_4 ?? faker.number.int({ min: 0, max: 3 }),\n  spell_slots_level_5: params.spell_slots_level_5 ?? faker.number.int({ min: 0, max: 2 }),\n  spell_slots_level_6: params.spell_slots_level_6 ?? faker.number.int({ min: 0, max: 1 }),\n  spell_slots_level_7: params.spell_slots_level_7 ?? faker.number.int({ min: 0, max: 1 }),\n  spell_slots_level_8: params.spell_slots_level_8 ?? faker.number.int({ min: 0, max: 1 }),\n  spell_slots_level_9: params.spell_slots_level_9 ?? faker.number.int({ min: 0, max: 1 }),\n  spells_known: params.spells_known ?? faker.number.int({ min: 0, max: 10 })\n}))\n\n// Factory for SubclassSpecific (optional fields)\nconst subclassSpecificFactory = Factory.define<SubclassSpecific>(() => {\n  const specifics: Partial<SubclassSpecific> = {}\n  if (faker.datatype.boolean(0.1))\n    specifics.additional_magical_secrets_max_lvl = faker.helpers.arrayElement([5, 7, 9])\n  if (faker.datatype.boolean(0.1)) specifics.aura_range = faker.helpers.arrayElement([10, 30])\n  return specifics\n})\n\n// --- Level Factory (Main) --- //\n\nexport const levelFactory = Factory.define<Omit<Level, '_id' | 'collectionName'>>(\n  ({ sequence, params }) => {\n    const level =\n      params.level ?? (sequence <= 20 ? sequence : faker.number.int({ min: 1, max: 20 }))\n\n    // Build APIReference dependencies first\n    const builtClass = apiReferenceFactory.build(params.class)\n    const builtSubclass = params.subclass ? apiReferenceFactory.build(params.subclass) : undefined\n    const builtFeatures = params.features\n      ? apiReferenceFactory.buildList(params.features.length)\n      : apiReferenceFactory.buildList(faker.number.int({ min: 0, max: 2 }))\n\n    const index =\n      params.index ??\n      createIndex(\n        builtSubclass ? `${builtSubclass.index}-level` : `${builtClass.index}-level`,\n        level\n      )\n    const url = params.url ?? createUrl('levels', index)\n\n    // Build potential complex nested objects - ensuring full structure\n    const shouldBuildClassSpecific =\n      params.class_specific !== undefined\n        ? params.class_specific !== null\n        : faker.datatype.boolean(0.5)\n    const builtClassSpecific = shouldBuildClassSpecific\n      ? classSpecificFactory.build(params.class_specific)\n      : undefined\n\n    const shouldBuildSpellcasting =\n      params.spellcasting !== undefined ? params.spellcasting !== null : faker.datatype.boolean(0.5)\n    const builtSpellcasting = shouldBuildSpellcasting\n      ? levelSpellcastingFactory.build(params.spellcasting)\n      : undefined\n\n    const shouldBuildSubclassSpecific =\n      builtSubclass != null &&\n      (params.subclass_specific !== undefined\n        ? params.subclass_specific !== null\n        : faker.datatype.boolean(0.3))\n    const builtSubclassSpecific = shouldBuildSubclassSpecific\n      ? subclassSpecificFactory.build(params.subclass_specific)\n      : undefined\n\n    return {\n      level,\n      index,\n      url,\n      class: {\n        // Explicit construction\n        index: builtClass.index,\n        name: builtClass.name,\n        url: builtClass.url\n      },\n      subclass: builtSubclass\n        ? {\n            // Explicit construction\n            index: builtSubclass.index,\n            name: builtSubclass.name,\n            url: builtSubclass.url\n          }\n        : undefined,\n      features: builtFeatures.map((f) => ({ index: f.index, name: f.name, url: f.url })), // Explicit construction\n      ability_score_bonuses: params.ability_score_bonuses ?? (level % 4 === 0 ? 1 : 0),\n      prof_bonus: params.prof_bonus ?? Math.floor((level - 1) / 4) + 2,\n      // Explicitly construct complex nested objects, providing defaults/undefined for all fields\n      class_specific: builtClassSpecific\n        ? {\n            action_surges: builtClassSpecific.action_surges,\n            arcane_recovery_levels: builtClassSpecific.arcane_recovery_levels,\n            aura_range: builtClassSpecific.aura_range,\n            bardic_inspiration_die: builtClassSpecific.bardic_inspiration_die,\n            brutal_critical_dice: builtClassSpecific.brutal_critical_dice,\n            channel_divinity_charges: builtClassSpecific.channel_divinity_charges,\n            creating_spell_slots: builtClassSpecific.creating_spell_slots?.map((s) =>\n              classSpecificCreatingSpellSlotFactory.build(s)\n            ), // Need factory for nested array\n            destroy_undead_cr: builtClassSpecific.destroy_undead_cr,\n            extra_attacks: builtClassSpecific.extra_attacks,\n            favored_enemies: builtClassSpecific.favored_enemies,\n            favored_terrain: builtClassSpecific.favored_terrain,\n            indomitable_uses: builtClassSpecific.indomitable_uses,\n            invocations_known: builtClassSpecific.invocations_known,\n            ki_points: builtClassSpecific.ki_points,\n            magical_secrets_max_5: builtClassSpecific.magical_secrets_max_5,\n            magical_secrets_max_7: builtClassSpecific.magical_secrets_max_7,\n            magical_secrets_max_9: builtClassSpecific.magical_secrets_max_9,\n            martial_arts: builtClassSpecific.martial_arts\n              ? classSpecificMartialArtFactory.build(builtClassSpecific.martial_arts)\n              : undefined,\n            metamagic_known: builtClassSpecific.metamagic_known,\n            mystic_arcanum_level_6: builtClassSpecific.mystic_arcanum_level_6,\n            mystic_arcanum_level_7: builtClassSpecific.mystic_arcanum_level_7,\n            mystic_arcanum_level_8: builtClassSpecific.mystic_arcanum_level_8,\n            mystic_arcanum_level_9: builtClassSpecific.mystic_arcanum_level_9,\n            rage_count: builtClassSpecific.rage_count,\n            rage_damage_bonus: builtClassSpecific.rage_damage_bonus,\n            sneak_attack: builtClassSpecific.sneak_attack\n              ? classSpecificSneakAttackFactory.build(builtClassSpecific.sneak_attack)\n              : undefined,\n            song_of_rest_die: builtClassSpecific.song_of_rest_die,\n            sorcery_points: builtClassSpecific.sorcery_points,\n            unarmored_movement: builtClassSpecific.unarmored_movement,\n            wild_shape_fly: builtClassSpecific.wild_shape_fly,\n            wild_shape_max_cr: builtClassSpecific.wild_shape_max_cr,\n            wild_shape_swim: builtClassSpecific.wild_shape_swim\n          }\n        : undefined,\n      spellcasting: builtSpellcasting\n        ? {\n            cantrips_known: builtSpellcasting.cantrips_known,\n            spell_slots_level_1: builtSpellcasting.spell_slots_level_1 ?? 0, // Ensure required fields have defaults\n            spell_slots_level_2: builtSpellcasting.spell_slots_level_2 ?? 0,\n            spell_slots_level_3: builtSpellcasting.spell_slots_level_3 ?? 0,\n            spell_slots_level_4: builtSpellcasting.spell_slots_level_4 ?? 0,\n            spell_slots_level_5: builtSpellcasting.spell_slots_level_5 ?? 0,\n            spell_slots_level_6: builtSpellcasting.spell_slots_level_6,\n            spell_slots_level_7: builtSpellcasting.spell_slots_level_7,\n            spell_slots_level_8: builtSpellcasting.spell_slots_level_8,\n            spell_slots_level_9: builtSpellcasting.spell_slots_level_9,\n            spells_known: builtSpellcasting.spells_known\n          }\n        : undefined,\n      subclass_specific: builtSubclassSpecific\n        ? {\n            additional_magical_secrets_max_lvl:\n              builtSubclassSpecific.additional_magical_secrets_max_lvl,\n            aura_range: builtSubclassSpecific.aura_range\n          }\n        : undefined,\n      updated_at: params.updated_at ?? faker.date.past().toISOString()\n    }\n  }\n)\n"
  },
  {
    "path": "src/tests/factories/2014/magicItem.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { apiReferenceFactory, createIndex, createUrl } from './common.factory'\n\nimport type { MagicItem, Rarity } from '@/models/2014/magicItem'\n\n// --- Rarity Factory ---\nconst rarityFactory = Factory.define<Rarity>(() => ({\n  name: faker.helpers.arrayElement(['Common', 'Uncommon', 'Rare', 'Very Rare', 'Legendary'])\n}))\n\n// --- MagicItem Factory ---\nexport const magicItemFactory = Factory.define<Omit<MagicItem, '_id' | 'collectionName'>>(\n  ({ sequence, params }) => {\n    const name = params.name ?? `Magic Item ${sequence}`\n    const index = params.index ?? createIndex(name)\n\n    // Build dependencies first to ensure complete objects\n    const builtEquipmentCategory = apiReferenceFactory.build(params.equipment_category)\n    const builtRarity = rarityFactory.build(params.rarity)\n\n    return {\n      index,\n      name,\n      desc: params.desc ?? [faker.lorem.paragraph()],\n      equipment_category: {\n        index: builtEquipmentCategory.index,\n        name: builtEquipmentCategory.name,\n        url: builtEquipmentCategory.url\n      },\n      rarity: {\n        name: builtRarity.name\n      },\n      variants: params.variants ?? [],\n      variant: params.variant ?? false,\n      url: params.url ?? createUrl('magic-items', index),\n      image: params.image ?? `/images/magic-items/${index}.png`,\n      updated_at: params.updated_at ?? faker.date.past().toISOString()\n    }\n  }\n)\n"
  },
  {
    "path": "src/tests/factories/2014/magicSchool.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { MagicSchool } from '@/models/2014/magicSchool'\n\nexport const magicSchoolFactory = Factory.define<MagicSchool>(({ sequence }) => {\n  const name = `Magic School ${sequence} - ${faker.lorem.words(1)}`\n  const index = name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n\n  return {\n    index: index,\n    name: name,\n    desc: faker.lorem.paragraph(),\n    url: `/api/magic-schools/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2014/monster.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\nimport mongoose from 'mongoose'\n\nimport {\n  apiReferenceFactory,\n  choiceFactory,\n  damageFactory,\n  difficultyClassFactory\n} from './common.factory'\n\nimport type {\n  ActionOption,\n  ActionUsage,\n  ArmorClassArmor,\n  ArmorClassCondition,\n  ArmorClassDex,\n  ArmorClassNatural,\n  ArmorClassSpell,\n  LegendaryAction,\n  Monster,\n  MonsterAction,\n  MonsterDocument,\n  MonsterProficiency,\n  MonsterSpeed,\n  Reaction,\n  Sense,\n  SpecialAbility,\n  SpecialAbilitySpell,\n  SpecialAbilitySpellcasting,\n  SpecialAbilityUsage\n} from '@/models/2014/monster'\n\n// Factory for ActionUsage\nconst actionUsageFactory = Factory.define<ActionUsage>(() => ({\n  type: faker.lorem.words(1),\n  dice: `${faker.number.int({ min: 1, max: 4 })}d${faker.number.int({ min: 4, max: 12 })}`,\n  min_value: faker.number.int({ min: 1, max: 10 })\n}))\n\n// Factory for ActionOption\nconst actionOptionFactory = Factory.define<ActionOption>(() => ({\n  action_name: faker.lorem.words(2),\n  count: faker.helpers.arrayElement([faker.number.int({ min: 1, max: 3 }), faker.lorem.word()]),\n  type: faker.helpers.arrayElement(['melee', 'ranged', 'ability', 'magic'])\n}))\n\n// Factory for Action\nconst actionFactory = Factory.define<\n  MonsterAction,\n  { has_damage?: boolean; has_dc?: boolean; has_attack_bonus?: boolean; has_usage?: boolean },\n  MonsterAction\n>(({ transientParams, associations }) => {\n  const generated_multiattack_type = faker.helpers.arrayElement(['actions', 'action_options'])\n\n  const baseAction = {\n    name: faker.lorem.words(2),\n    desc: faker.lorem.paragraph(),\n    attack_bonus:\n      transientParams?.has_attack_bonus === true\n        ? faker.number.int({ min: 0, max: 10 })\n        : undefined,\n    damage:\n      associations.damage ??\n      (transientParams?.has_damage === true\n        ? damageFactory.buildList(faker.number.int({ min: 1, max: 2 }))\n        : []),\n    dc:\n      associations.dc ??\n      (transientParams?.has_dc === true ? difficultyClassFactory.build() : undefined),\n    options: undefined, // Assuming Action['options'] is optional or handled elsewhere\n    usage:\n      associations.usage ??\n      (transientParams?.has_usage === true ? actionUsageFactory.build() : undefined)\n  }\n\n  if (generated_multiattack_type === 'actions') {\n    return {\n      ...baseAction,\n      multiattack_type: 'actions',\n      actions: actionOptionFactory.buildList(faker.number.int({ min: 1, max: 3 })),\n      action_options: choiceFactory.build()\n    } as MonsterAction\n  } else {\n    return {\n      ...baseAction,\n      multiattack_type: 'action_options',\n      actions: [],\n      action_options: choiceFactory.build()\n    } as MonsterAction\n  }\n})\n\n// Factory for ArmorClass (Union Type - need specific factories or a generic one)\n// Example for 'natural' type\n// const armorClassNaturalFactory = Factory.define<ArmorClassNatural>(() => ({\n//   type: 'natural',\n//   value: faker.number.int({ min: 10, max: 20 }),\n//   desc: faker.datatype.boolean() ? faker.lorem.sentence() : undefined\n// }))\n\n// Example for 'armor' type\n// const armorClassArmorFactory = Factory.define<ArmorClassArmor>(({ associations }) => ({\n//   type: 'armor',\n//   value: faker.number.int({ min: 12, max: 18 }),\n//   armor: associations.armor\n//     ? associations.armor\n//     : apiReferenceFactory.buildList(faker.number.int({ min: 0, max: 1 })),\n//   desc: faker.datatype.boolean() ? faker.lorem.sentence() : undefined\n// }))\n\n// A helper to create a random ArmorClass type\nconst armorClassFactory = Factory.define<\n  ArmorClassDex | ArmorClassNatural | ArmorClassArmor | ArmorClassSpell | ArmorClassCondition\n>(() => {\n  const type = faker.helpers.arrayElement(['dex', 'natural', 'armor', 'spell', 'condition'])\n  const value = faker.number.int({ min: 10, max: 25 })\n  const desc = faker.datatype.boolean() ? faker.lorem.sentence() : undefined\n\n  switch (type) {\n    case 'dex':\n      return { type, value, desc }\n    case 'natural':\n      return { type, value, desc }\n    case 'armor':\n      return {\n        type,\n        value,\n        desc,\n        armor: apiReferenceFactory.buildList(faker.number.int({ min: 0, max: 1 }))\n      }\n    case 'spell':\n      return { type, value, desc, spell: apiReferenceFactory.build() }\n    case 'condition':\n      return { type, value, desc, condition: apiReferenceFactory.build() }\n    default: // Should not happen with the defined types\n      return { type: 'natural', value: 10 } // Fallback\n  }\n})\n\n// Factory for LegendaryAction\nconst legendaryActionFactory = Factory.define<LegendaryAction>(({ associations }) => ({\n  name: faker.lorem.words(3),\n  desc: faker.lorem.sentence(),\n  attack_bonus: faker.datatype.boolean() ? faker.number.int({ min: 0, max: 12 }) : undefined,\n  damage: associations.damage\n    ? associations.damage\n    : damageFactory.buildList(faker.number.int({ min: 0, max: 2 })),\n  dc: associations.dc\n    ? associations.dc\n    : faker.datatype.boolean()\n      ? difficultyClassFactory.build()\n      : undefined\n}))\n\n// Factory for Proficiency\nconst proficiencyFactory = Factory.define<MonsterProficiency>(({ associations }) => ({\n  proficiency: apiReferenceFactory.build(associations.proficiency),\n  value: faker.number.int({ min: 1, max: 10 })\n}))\n\n// Factory for Reaction\nconst reactionFactory = Factory.define<Reaction>(({ associations }) => ({\n  name: faker.lorem.words(2),\n  desc: faker.lorem.sentence(),\n  dc: associations.dc\n    ? associations.dc\n    : faker.datatype.boolean()\n      ? difficultyClassFactory.build()\n      : undefined\n}))\n\n// Factory for Sense\nconst senseFactory = Factory.define<Sense>(() => ({\n  blindsight: faker.datatype.boolean()\n    ? `${faker.number.int({ min: 10, max: 120 })} ft.`\n    : undefined,\n  darkvision: faker.datatype.boolean()\n    ? `${faker.number.int({ min: 30, max: 120 })} ft.`\n    : undefined,\n  passive_perception: faker.number.int({ min: 8, max: 25 }),\n  tremorsense: faker.datatype.boolean()\n    ? `${faker.number.int({ min: 10, max: 60 })} ft.`\n    : undefined,\n  truesight: faker.datatype.boolean() ? `${faker.number.int({ min: 10, max: 120 })} ft.` : undefined\n}))\n\n// Factory for SpecialAbilityUsage\nconst specialAbilityUsageFactory = Factory.define<SpecialAbilityUsage>(() => ({\n  type: faker.helpers.arrayElement([\n    'At Will',\n    'Per Day',\n    'Recharge after Rest',\n    'Recharge on Roll'\n  ]),\n  times: faker.datatype.boolean() ? faker.number.int({ min: 1, max: 3 }) : undefined,\n  rest_types: faker.datatype.boolean()\n    ? faker.helpers.arrayElements(['short', 'long'], faker.number.int({ min: 1, max: 2 }))\n    : undefined\n}))\n\n// Factory for SpecialAbilitySpell\nconst specialAbilitySpellFactory = Factory.define<SpecialAbilitySpell>(({ associations }) => ({\n  name: faker.lorem.words(3),\n  level: faker.number.int({ min: 0, max: 9 }),\n  url: `/api/spells/${faker.lorem.slug()}`,\n  notes: faker.datatype.boolean() ? faker.lorem.sentence() : undefined,\n  usage: associations.usage\n    ? associations.usage\n    : faker.datatype.boolean()\n      ? specialAbilityUsageFactory.build()\n      : undefined\n}))\n\n// Factory for SpecialAbilitySpellcasting\nconst specialAbilitySpellcastingFactory = Factory.define<SpecialAbilitySpellcasting>(\n  ({ associations }) => {\n    // Generate key-value pairs first\n    const slotEntries: [string, number][] = []\n    for (let i = 1; i <= 9; i++) {\n      if (faker.datatype.boolean()) {\n        slotEntries.push([i.toString(), faker.number.int({ min: 1, max: 4 })])\n      }\n    }\n    // Create a plain object from the entries, matching the new schema type\n    const slotsObject = slotEntries.length > 0 ? Object.fromEntries(slotEntries) : undefined\n\n    return {\n      level: faker.datatype.boolean() ? faker.number.int({ min: 1, max: 20 }) : undefined,\n      ability: apiReferenceFactory.build(\n        associations.ability ?? { index: faker.helpers.arrayElement(['int', 'wis', 'cha']) }\n      ),\n      dc: faker.datatype.boolean() ? faker.number.int({ min: 10, max: 20 }) : undefined,\n      modifier: faker.datatype.boolean() ? faker.number.int({ min: 0, max: 5 }) : undefined,\n      components_required: faker.helpers.arrayElements(\n        ['V', 'S', 'M'],\n        faker.number.int({ min: 1, max: 3 })\n      ),\n      school: faker.datatype.boolean() ? faker.lorem.word() : undefined,\n      // Assign the plain object\n      slots: slotsObject,\n      spells: specialAbilitySpellFactory.buildList(faker.number.int({ min: 1, max: 5 }))\n    }\n  }\n)\n\n// Factory for SpecialAbility\nconst specialAbilityFactory = Factory.define<SpecialAbility>(({ associations }) => {\n  const usage = associations.usage ? associations.usage : specialAbilityUsageFactory.build()\n\n  return {\n    name: faker.lorem.words(2),\n    desc: faker.lorem.sentence(),\n    attack_bonus: faker.datatype.boolean() ? faker.number.int({ min: -1, max: 8 }) : undefined,\n    damage: associations.damage\n      ? associations.damage\n      : damageFactory.buildList(faker.number.int({ min: 0, max: 1 })),\n    dc: associations.dc\n      ? associations.dc\n      : faker.datatype.boolean()\n        ? difficultyClassFactory.build()\n        : undefined,\n    spellcasting: associations.spellcasting\n      ? associations.spellcasting\n      : faker.datatype.boolean()\n        ? specialAbilitySpellcastingFactory.build()\n        : undefined,\n    usage: usage\n  }\n})\n\n// Factory for Speed\nconst speedFactory = Factory.define<MonsterSpeed>(() => {\n  const speeds: Partial<MonsterSpeed> = {}\n  if (faker.datatype.boolean()) speeds.walk = `${faker.number.int({ min: 10, max: 60 })} ft.`\n  if (faker.datatype.boolean()) speeds.swim = `${faker.number.int({ min: 10, max: 60 })} ft.`\n  if (faker.datatype.boolean()) speeds.fly = `${faker.number.int({ min: 10, max: 60 })} ft.`\n  if (faker.datatype.boolean()) speeds.burrow = `${faker.number.int({ min: 10, max: 60 })} ft.`\n  if (faker.datatype.boolean()) speeds.climb = `${faker.number.int({ min: 10, max: 60 })} ft.`\n  if (faker.datatype.boolean()) speeds.hover = faker.datatype.boolean()\n  // Ensure at least one speed exists\n  if (Object.keys(speeds).length === 0 || (Object.keys(speeds).length === 1 && 'hover' in speeds)) {\n    speeds.walk = `${faker.number.int({ min: 10, max: 60 })} ft.`\n  }\n  return speeds as MonsterSpeed // Cast needed because we build it partially\n})\n\n// Factory for Monster - Define return type explicitly as Monster\nconst monsterFactory = Factory.define<Monster, any, Monster>(\n  ({ associations, transientParams }) => {\n    const size = faker.helpers.arrayElement([\n      'Tiny',\n      'Small',\n      'Medium',\n      'Large',\n      'Huge',\n      'Gargantuan'\n    ])\n    const type = faker.lorem.word() // Consider using helpers.arrayElement for specific types if needed\n    const subtype = faker.datatype.boolean() ? faker.lorem.word() : undefined\n    const alignment = faker.helpers.arrayElement([\n      'chaotic neutral',\n      'chaotic evil',\n      'chaotic good',\n      'lawful neutral',\n      'lawful evil',\n      'lawful good',\n      'neutral',\n      'neutral evil',\n      'neutral good',\n      'any alignment',\n      'unaligned'\n    ])\n    const slug = transientParams?.index ?? faker.lorem.slug() // Use transient index if provided\n\n    // Return a plain object matching the Monster interface\n    return {\n      index: slug,\n      name: transientParams?.name ?? faker.person.firstName(), // Use transient name if provided\n      desc: faker.lorem.paragraph(),\n      size: size,\n      type: type,\n      subtype: subtype,\n      alignment: alignment,\n      armor_class:\n        associations.armor_class ??\n        armorClassFactory.buildList(faker.number.int({ min: 1, max: 2 })),\n      hit_points: faker.number.int({ min: 10, max: 300 }),\n      hit_dice: `${faker.number.int({ min: 1, max: 20 })}d${faker.number.int({ min: 4, max: 12 })}`,\n      hit_points_roll: `${faker.number.int({ min: 1, max: 20 })}d${faker.number.int({ min: 4, max: 12 })} + ${faker.number.int({ min: 0, max: 50 })}`,\n      speed: associations.speed ?? speedFactory.build(),\n      strength: faker.number.int({ min: 3, max: 30 }),\n      dexterity: faker.number.int({ min: 3, max: 30 }),\n      constitution: faker.number.int({ min: 3, max: 30 }),\n      intelligence: faker.number.int({ min: 3, max: 30 }),\n      wisdom: faker.number.int({ min: 3, max: 30 }),\n      charisma: faker.number.int({ min: 3, max: 30 }),\n      proficiencies:\n        associations.proficiencies ??\n        proficiencyFactory.buildList(faker.number.int({ min: 0, max: 5 })),\n      damage_vulnerabilities: Array.from({ length: faker.number.int({ min: 0, max: 2 }) }, () =>\n        faker.lorem.word()\n      ),\n      damage_resistances: Array.from({ length: faker.number.int({ min: 0, max: 3 }) }, () =>\n        faker.lorem.word()\n      ),\n      damage_immunities: Array.from({ length: faker.number.int({ min: 0, max: 3 }) }, () =>\n        faker.lorem.word()\n      ),\n      condition_immunities: apiReferenceFactory.buildList(\n        associations.condition_immunities?.length ?? faker.number.int({ min: 0, max: 3 }),\n        associations.condition_immunities?.[0] // Pass potential overrides\n      ),\n      senses: associations.senses ?? senseFactory.build(),\n      languages: transientParams?.languages ?? 'Common', // Add languages field - defaulting to \"Common\", allow override\n      challenge_rating:\n        transientParams?.challenge_rating ??\n        faker.helpers.arrayElement([\n          0, 0.125, 0.25, 0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,\n          20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30\n        ]),\n      proficiency_bonus: faker.number.int({ min: 2, max: 9 }),\n      xp: faker.number.int({ min: 10, max: 155000 }),\n      special_abilities:\n        associations.special_abilities ??\n        specialAbilityFactory.buildList(faker.number.int({ min: 0, max: 4 })),\n      actions:\n        associations.actions ??\n        actionFactory.buildList(\n          faker.number.int({ min: 1, max: 5 }),\n          {},\n          { transient: { has_damage: true } }\n        ),\n      legendary_actions:\n        associations.legendary_actions ??\n        legendaryActionFactory.buildList(faker.number.int({ min: 0, max: 3 })),\n      image: faker.datatype.boolean() ? `/api/images/monsters/${slug}.png` : undefined,\n      reactions:\n        transientParams?.has_reactions === true\n          ? reactionFactory.buildList(faker.number.int({ min: 1, max: 2 }))\n          : undefined,\n      url: `/api/monsters/${slug}`,\n      updated_at: faker.date.recent().toISOString()\n    }\n  }\n)\n\nexport const monsterModelFactory = Factory.define<MonsterDocument>(\n  ({ associations, transientParams }) => {\n    // sequence is implicitly passed via the GeneratorFnOptions, not BuildOptions\n    const monsterProps = monsterFactory.build(associations, { transient: transientParams })\n    const doc = {\n      _id: new mongoose.Types.ObjectId(),\n      __v: 0,\n      ...monsterProps\n    } as MonsterDocument\n    return doc\n  }\n)\n\nexport { monsterFactory }\n"
  },
  {
    "path": "src/tests/factories/2014/proficiency.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Proficiency } from '@/models/2014/proficiency'\n\nimport { apiReferenceFactory } from './common.factory'\n\n// Main Proficiency factory\nexport const proficiencyFactory = Factory.define<Proficiency>(({ sequence }) => {\n  const name = `Proficiency ${sequence} - ${faker.lorem.words(2)}`\n  const index = name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n  const type = faker.helpers.arrayElement([\n    'saving-throws',\n    'skills',\n    'armor',\n    'weapons',\n    'tools',\n    'languages'\n  ])\n\n  return {\n    index: index,\n    name: name,\n    type: type,\n    reference: apiReferenceFactory.build(),\n    url: `/api/proficiencies/${index}`,\n    updated_at: faker.date.recent().toISOString(),\n    classes: apiReferenceFactory.buildList(faker.number.int({ min: 0, max: 2 })),\n    races: apiReferenceFactory.buildList(faker.number.int({ min: 0, max: 1 }))\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2014/race.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { apiReferenceFactory, choiceFactory } from './common.factory'\n\nimport type { Race, RaceAbilityBonus } from '@/models/2014/race'\n\n// Factory for the nested RaceAbilityBonus\nconst raceAbilityBonusFactory = Factory.define<RaceAbilityBonus>(({ associations }) => ({\n  ability_score: associations.ability_score ?? apiReferenceFactory.build(),\n  bonus: faker.number.int({ min: 1, max: 2 })\n}))\n\n// Factory for Race\nexport const raceFactory = Factory.define<Race>(({ sequence, associations, transientParams }) => {\n  const name = transientParams?.name ?? `Race ${sequence}`\n  const index = name.toLowerCase().replace(/ /g, '-')\n\n  return {\n    index,\n    name,\n    speed: faker.number.int({ min: 25, max: 35 }),\n    ability_bonuses: raceAbilityBonusFactory.buildList(faker.number.int({ min: 1, max: 3 })),\n    ability_bonus_options:\n      associations.ability_bonus_options ??\n      (faker.datatype.boolean() ? choiceFactory.build() : undefined),\n    alignment: faker.lorem.paragraph(),\n    age: faker.lorem.paragraph(),\n    size: faker.helpers.arrayElement(['Small', 'Medium', 'Large']),\n    size_description: faker.lorem.paragraph(),\n    languages:\n      associations.languages ??\n      apiReferenceFactory.buildList(\n        faker.number.int({ min: 1, max: 3 }),\n        {},\n        { transient: { resourceType: 'languages' } }\n      ),\n    language_options: associations.language_options ?? choiceFactory.build(), // Required\n    language_desc: faker.lorem.paragraph(),\n    traits:\n      associations.traits ??\n      apiReferenceFactory.buildList(\n        faker.number.int({ min: 0, max: 4 }),\n        {},\n        { transient: { resourceType: 'traits' } }\n      ),\n    subraces:\n      associations.subraces ??\n      apiReferenceFactory.buildList(\n        faker.number.int({ min: 0, max: 2 }),\n        {},\n        { transient: { resourceType: 'subraces' } }\n      ),\n    url: `/api/races/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2014/rule.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Rule } from '@/models/2014/rule'\n\nimport { apiReferenceFactory } from './common.factory' // Import common factory\n\nexport const ruleFactory = Factory.define<Rule>(({ sequence }) => {\n  const name = `Rule ${sequence} - ${faker.lorem.words(2)}`\n  const index = name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n\n  return {\n    index: index,\n    name: name,\n    desc: faker.lorem.paragraph(),\n    // Build a list of subsection references\n    subsections: apiReferenceFactory.buildList(faker.number.int({ min: 0, max: 3 })),\n    url: `/api/rules/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2014/ruleSection.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { RuleSection } from '@/models/2014/ruleSection'\n\nexport const ruleSectionFactory = Factory.define<RuleSection>(({ sequence }) => {\n  const name = `Rule Section ${sequence} - ${faker.lorem.words(3)}`\n  const index = name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n\n  return {\n    index: index,\n    name: name,\n    desc: faker.lorem.paragraph(), // Single string\n    url: `/api/rule-sections/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2014/skill.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Skill } from '@/models/2014/skill'\n\nimport { apiReferenceFactory } from './common.factory' // Import common factory\n\nexport const skillFactory = Factory.define<Skill>(({ sequence }) => {\n  const name = `Skill ${sequence} - ${faker.lorem.words(2)}`\n  const index = name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n\n  return {\n    index: index,\n    name: name,\n    desc: [faker.lorem.paragraph()],\n    // Build a default ability score using the common factory\n    ability_score: apiReferenceFactory.build({\n      index: faker.helpers.arrayElement(['str', 'dex', 'con', 'int', 'wis', 'cha']),\n      name: faker.helpers.arrayElement(['STR', 'DEX', 'CON', 'INT', 'WIS', 'CHA']),\n      url: faker.internet.url()\n    }),\n    url: `/api/skills/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2014/spell.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Spell, SpellDamage, SpellDC } from '@/models/2014/spell'\n\nimport { apiReferenceFactory, areaOfEffectFactory } from './common.factory'\n\n// --- Sub-factories (Placeholders/Simple Defaults) ---\nconst spellDamageFactory = Factory.define<SpellDamage>(() => ({\n  // Defaulting to undefined. Tests needing damage must build it.\n  damage_type: undefined,\n  damage_at_slot_level: undefined,\n  damage_at_character_level: undefined\n}))\n\nconst spellDcFactory = Factory.define<SpellDC>(() => ({\n  dc_type: apiReferenceFactory.build(),\n  dc_success: faker.helpers.arrayElement(['none', 'half']),\n  desc: undefined // Optional\n}))\n\n// --- Main Spell Factory ---\nexport const spellFactory = Factory.define<Spell>(({ sequence }) => {\n  const name = `Spell ${sequence} - ${faker.lorem.words(2)}`\n  const index = name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n  const level = faker.number.int({ min: 0, max: 9 })\n\n  return {\n    index: index,\n    name: name,\n    desc: [faker.lorem.paragraph()],\n    level: level,\n    attack_type: faker.datatype.boolean(0.5)\n      ? faker.helpers.arrayElement(['melee', 'ranged'])\n      : undefined,\n    casting_time: faker.word.noun(),\n    ritual: faker.datatype.boolean(),\n    duration: faker.helpers.arrayElement(['Instantaneous', '1 round', '1 minute']),\n    concentration: faker.datatype.boolean(),\n    components: faker.helpers.arrayElements(['V', 'S', 'M'], faker.number.int({ min: 1, max: 3 })),\n    range: faker.helpers.arrayElement(['Self', 'Touch', '60 feet']),\n    school: apiReferenceFactory.build(),\n    classes: apiReferenceFactory.buildList(faker.number.int({ min: 1, max: 2 })),\n    subclasses: apiReferenceFactory.buildList(faker.number.int({ min: 0, max: 1 })),\n    url: `/api/spells/${index}`,\n    updated_at: faker.date.recent().toISOString(),\n\n    // Optional/Complex fields - Defaulted to undefined or simple placeholders\n    // Tests needing specific values must override.\n    higher_level: undefined,\n    material: undefined,\n    damage: spellDamageFactory.build(),\n    dc: spellDcFactory.build(),\n    heal_at_slot_level: undefined,\n    area_of_effect: areaOfEffectFactory.build()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2014/subclass.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { apiReferenceFactory, createIndex, createUrl } from './common.factory'\n\nimport type { Prerequisite, Subclass, SubclassSpell } from '@/models/2014/subclass'\n\n// --- Prerequisite Factory ---\nconst prerequisiteFactory = Factory.define<Prerequisite>(({ params }) => ({\n  index: params.index ?? createIndex(faker.word.adjective()),\n  name: params.name ?? faker.word.adjective(),\n  type: params.type ?? 'spell',\n  url: params.url ?? createUrl('testing', params.index ?? createIndex(faker.word.adjective()))\n}))\n\nconst subclassSpellFactory = Factory.define<SubclassSpell>(({ params }) => {\n  // Build dependencies first\n  const builtPrereqs = prerequisiteFactory.buildList(\n    params.prerequisites?.length ?? faker.number.int({ min: 0, max: 1 })\n  )\n  const builtSpell = apiReferenceFactory.build(params.spell)\n\n  return {\n    prerequisites: builtPrereqs.map((p) => ({\n      index: p.index,\n      name: p.name,\n      type: p.type,\n      url: p.url\n    })),\n    spell: builtSpell\n  }\n})\n\n// --- Subclass Factory ---\nexport const subclassFactory = Factory.define<Omit<Subclass, '_id' | 'collectionName'>>(\n  ({ sequence, params }) => {\n    const name = params.name ?? `${faker.word.adjective()} Subclass ${sequence}`\n    const index = params.index ?? createIndex(name)\n\n    // Build dependencies\n    const builtClass = apiReferenceFactory.build(params.class)\n    // Optional spells - build list only if params.spells is provided or randomly\n    const spells =\n      params.spells ??\n      (faker.datatype.boolean(0.3)\n        ? subclassSpellFactory.buildList(faker.number.int({ min: 1, max: 5 }))\n        : undefined)\n\n    return {\n      index,\n      name,\n      class: builtClass,\n      subclass_flavor: params.subclass_flavor ?? faker.lorem.words(3),\n      desc: params.desc ?? [faker.lorem.paragraph()],\n      subclass_levels: params.subclass_levels ?? `/api/subclasses/${index}/levels`,\n      spells: spells?.map((s: SubclassSpell) => ({\n        prerequisites: s.prerequisites,\n        spell: s.spell\n      })),\n      url: params.url ?? createUrl('subclasses', index),\n      updated_at: params.updated_at ?? faker.date.past().toISOString()\n    }\n  }\n)\n"
  },
  {
    "path": "src/tests/factories/2014/subrace.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { apiReferenceFactory } from './common.factory'\n\nimport type { Subrace, SubraceAbilityBonus } from '@/models/2014/subrace'\n\n// Factory for the nested SubraceAbilityBonus\nconst subraceAbilityBonusFactory = Factory.define<SubraceAbilityBonus>(({ associations }) => ({\n  ability_score: associations.ability_score ?? apiReferenceFactory.build(),\n  bonus: faker.number.int({ min: 1, max: 2 })\n}))\n\n// Factory for Subrace\nexport const subraceFactory = Factory.define<Subrace>(\n  ({ sequence, associations, transientParams }) => {\n    const name = transientParams?.name ?? `Subrace ${sequence}`\n    const index = name.toLowerCase().replace(/ /g, '-')\n\n    return {\n      index: index,\n      name: name,\n      race:\n        associations.race ??\n        apiReferenceFactory.build({}, { transient: { resourceType: 'races' } }),\n      desc: faker.lorem.paragraph(),\n      ability_bonuses: subraceAbilityBonusFactory.buildList(faker.number.int({ min: 1, max: 2 })),\n      racial_traits:\n        associations.racial_traits ??\n        apiReferenceFactory.buildList(\n          faker.number.int({ min: 0, max: 3 }),\n          {},\n          { transient: { resourceType: 'traits' } }\n        ),\n      url: `/api/subraces/${index}`,\n      updated_at: faker.date.recent().toISOString()\n    }\n  }\n)\n"
  },
  {
    "path": "src/tests/factories/2014/trait.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Trait } from '@/models/2014/trait'\n\nimport { apiReferenceFactory, choiceFactory } from './common.factory'\n\nexport const traitFactory = Factory.define<Trait>(({ sequence }) => {\n  const name = `Trait ${sequence} - ${faker.lorem.words(2)}`\n  const index = name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n\n  return {\n    index: index,\n    name: name,\n    desc: [faker.lorem.paragraph()],\n    races: [apiReferenceFactory.build()], // Default with one race\n    subraces: [apiReferenceFactory.build()], // Default with one subrace\n    url: `/api/traits/${index}`,\n    updated_at: faker.date.recent().toISOString(),\n\n    // Optional fields defaulted for basic structure\n    // Tests needing specific values should override or build manually\n    languages: [],\n    proficiencies: [],\n    language_options: choiceFactory.build(),\n    proficiency_choices: choiceFactory.build(),\n    parent: apiReferenceFactory.build(),\n    trait_specific: undefined\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2014/weaponProperty.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport type { WeaponProperty } from '@/models/2014/weaponProperty'\n\nexport const weaponPropertyFactory = Factory.define<WeaponProperty>(\n  ({ sequence, transientParams }) => {\n    const name = transientParams?.name ?? `Weapon Property ${sequence}`\n    const index = name.toLowerCase().replace(/\\s+/g, '-')\n\n    return {\n      index,\n      name,\n      desc: [faker.lorem.paragraph()], // desc is string[]\n      url: `/api/weapon-properties/${index}`,\n      updated_at: faker.date.recent().toISOString()\n    }\n  }\n)\n"
  },
  {
    "path": "src/tests/factories/2024/abilityScore.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { AbilityScore2024 } from '@/models/2024/abilityScore'\n\nimport { apiReferenceFactory } from './common.factory'\n\n// Define the factory using fishery\nexport const abilityScoreFactory = Factory.define<AbilityScore2024>(\n  ({ sequence, params, transientParams }) => {\n    // params are overrides\n    // transientParams are params not part of the final object, useful for intermediate logic\n    // sequence provides a unique number for each generated object\n\n    // Use transientParams for defaults that might be complex or used multiple times\n    const name = params.name ?? transientParams.baseName ?? `Ability Score ${sequence}`\n    const index = params.index ?? name.toLowerCase().replace(/\\s+/g, '-')\n\n    return {\n      // Required fields\n      index,\n      name,\n      full_name: params.full_name ?? `Full ${name}`,\n      description: params.description ?? faker.lorem.paragraph(), // Simplified default\n      url: params.url ?? `/api/ability-scores/${index}`,\n      updated_at: params.updated_at ?? faker.date.recent().toISOString(),\n\n      // Non-required fields - Use the imported factory\n      skills: params.skills ?? apiReferenceFactory.buildList(2), // Build a list of 2 APIReferences\n\n      // Merging params ensures overrides work correctly\n      ...params\n    }\n  }\n)\n"
  },
  {
    "path": "src/tests/factories/2024/alignment.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Alignment2024 } from '@/models/2024/alignment'\n\nexport const alignmentFactory = Factory.define<Alignment2024>(() => {\n  const name = faker.lorem.words()\n  const index = name.toLowerCase().replace(/\\s+/g, '-')\n\n  return {\n    index,\n    name,\n    abbreviation: name.substring(0, 2).toUpperCase(),\n    description: faker.lorem.paragraph(),\n    url: `/api/alignments/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2024/background.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Background2024, BackgroundFeatReference } from '@/models/2024/background'\n\nimport { apiReferenceFactory } from './common.factory'\n\nexport const backgroundFeatReferenceFactory = Factory.define<BackgroundFeatReference>(\n  ({ sequence }) => {\n    const name = `Feat ${sequence}`\n    const index = name.toLowerCase().replace(/[^a-z0-9]+/g, '-')\n    return {\n      index,\n      name,\n      url: `/api/2024/feats/${index}`\n    }\n  }\n)\n\nexport const backgroundFactory = Factory.define<Background2024>(({ sequence }) => {\n  const name = `Background ${sequence} - ${faker.lorem.word()}`\n  const index = name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n\n  return {\n    index,\n    name,\n    ability_scores: apiReferenceFactory.buildList(2, {}, { transient: {} }),\n    feat: backgroundFeatReferenceFactory.build(),\n    proficiencies: apiReferenceFactory.buildList(2, {}, { transient: {} }),\n    url: `/api/2024/backgrounds/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2024/collection.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { createIndex } from './common.factory'\n\nimport type { Collection2024 } from '@/models/2024/collection'\n\n// Factory only needs to define properties present in the Collection model\nexport const collectionFactory = Factory.define<Omit<Collection2024, '_id' | 'collectionName'>>(\n  ({ sequence, params }) => {\n    // Generate a plausible index, or use one provided\n    const index = params.index ?? createIndex(`${faker.word.noun()} ${sequence}`)\n\n    return {\n      index\n    }\n  }\n)\n"
  },
  {
    "path": "src/tests/factories/2024/common.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { APIReference } from '@/models/common/apiReference'\nimport { AreaOfEffect } from '@/models/common/areaOfEffect'\nimport { Choice, OptionsArrayOptionSet, StringOption } from '@/models/common/choice'\nimport { Damage } from '@/models/common/damage'\nimport { DifficultyClass } from '@/models/common/difficultyClass'\n\n// --- Helper Functions ---\nexport const createIndex = (name: string): string =>\n  name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n\nexport const createUrl = (resource: string, index: string): string =>\n  `/api/2024/${resource}/${index}`\n\n// --- APIReference ---\nexport const apiReferenceFactory = Factory.define<APIReference>(({ sequence, params }) => {\n  const name = params?.name ?? `Reference ${sequence}`\n  const index = params?.index ?? createIndex(name)\n  // Default to a generic 'testing' resource if not provided\n  const resource = params?.url?.split('/')[3] ?? 'testing'\n  return {\n    index: index,\n    name: name,\n    url: params?.url ?? createUrl(resource, index)\n  }\n})\n\n// --- AreaOfEffect ---\nexport const areaOfEffectFactory = Factory.define<AreaOfEffect>(() => ({\n  size: faker.number.int({ min: 5, max: 30 }),\n  type: faker.helpers.arrayElement(['sphere', 'cube', 'cylinder', 'line', 'cone'])\n}))\n\n// --- DifficultyClass ---\nexport const difficultyClassFactory = Factory.define<DifficultyClass>(() => ({\n  dc_type: apiReferenceFactory.build(),\n  dc_value: faker.number.int({ min: 10, max: 25 }),\n  success_type: faker.helpers.arrayElement(['none', 'half', 'other'])\n}))\n\n// --- Damage ---\nexport const damageFactory = Factory.define<Damage>(() => ({\n  damage_type: apiReferenceFactory.build(),\n  damage_dice: `${faker.number.int({ min: 1, max: 4 })}d${faker.helpers.arrayElement([\n    4, 6, 8, 10, 12\n  ])}`\n}))\n\n// --- Option (using StringOption as a simple representative) ---\n// Tests needing specific option types will need dedicated factories or manual construction\nexport const stringOptionFactory = Factory.define<StringOption>(({ sequence }) => ({\n  option_type: 'string',\n  string: `Option String ${sequence}`\n}))\n\n// --- OptionSet (using OptionsArrayOptionSet as representative) ---\n// Tests needing specific option set types will need dedicated factories or manual construction\nexport const optionsArrayOptionSetFactory = Factory.define<OptionsArrayOptionSet>(() => ({\n  option_set_type: 'options_array',\n  options: stringOptionFactory.buildList(1) // Default with one simple string option\n}))\n\n// --- Choice (Simplified) ---\n// This now uses the more concrete optionsArrayOptionSetFactory\nexport const choiceFactory = Factory.define<Choice>(() => ({\n  desc: faker.lorem.sentence(),\n  choose: 1,\n  type: 'equipment', // Default type\n  from: optionsArrayOptionSetFactory.build() // Use the concrete subtype factory\n}))\n\n// --- Other Option Subtypes (Placeholders - build as needed) ---\n// export const referenceOptionFactory = Factory.define<ReferenceOption>(...)\n// export const actionOptionFactory = Factory.define<ActionOption>(...)\n// export const multipleOptionFactory = Factory.define<MultipleOption>(...)\n// export const idealOptionFactory = Factory.define<IdealOption>(...)\n// export const countedReferenceOptionFactory = Factory.define<CountedReferenceOption>(...)\n// ... etc.\n"
  },
  {
    "path": "src/tests/factories/2024/condition.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Condition2024 } from '@/models/2024/condition'\n\nexport const conditionFactory = Factory.define<Condition2024>(() => {\n  const name = faker.lorem.words()\n  const index = name.toLowerCase().replace(/\\s+/g, '-')\n\n  return {\n    index,\n    name,\n    description: faker.lorem.paragraph(),\n    url: `/api/conditions/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2024/damageType.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { DamageType2024 } from '@/models/2024/damageType'\n\nexport const damageTypeFactory = Factory.define<DamageType2024>(() => {\n  const name = faker.lorem.words()\n  const index = name.toLowerCase().replace(/\\s+/g, '-')\n\n  return {\n    index,\n    name,\n    description: faker.lorem.paragraph(),\n    url: `/api/damage-types/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2024/equipment.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Cost, Equipment2024, Range, ThrowRange } from '@/models/2024/equipment'\n\nimport { apiReferenceFactory, createIndex, damageFactory } from './common.factory'\n\n// --- Sub-factories ---\n\nconst costFactory = Factory.define<Cost>(() => ({\n  quantity: faker.number.int({ min: 1, max: 1000 }),\n  unit: faker.helpers.arrayElement(['cp', 'sp', 'gp'])\n}))\n\nconst rangeFactory = Factory.define<Range>(() => ({\n  normal: faker.number.int({ min: 5, max: 100 }),\n  long: faker.number.int({ min: 10, max: 200 })\n}))\n\nconst throwRangeFactory = Factory.define<ThrowRange>(() => ({\n  normal: faker.number.int({ min: 5, max: 30 }),\n  long: faker.number.int({ min: 31, max: 120 })\n}))\n\n// --- Main Equipment Factory ---\nexport const equipmentFactory = Factory.define<Equipment2024>(({ sequence, params }) => {\n  const name = params.name ?? `Equipment ${sequence} - ${faker.commerce.productName()}`\n  const index = createIndex(name)\n\n  return {\n    index,\n    name,\n    description: [faker.lorem.sentence()],\n    equipment_categories: apiReferenceFactory.buildList(\n      1,\n      {},\n      { transient: { resource: 'equipment-categories' } }\n    ),\n    cost: costFactory.build(),\n    url: `/api/2024/equipment/${index}`,\n    updated_at: faker.date.recent().toISOString(),\n\n    // Optional fields - should be added by specific tests\n    ammunition: undefined,\n    weight: undefined,\n    damage: undefined,\n    image: undefined,\n    mastery: undefined,\n    notes: undefined,\n    properties: undefined,\n    quantity: undefined,\n    range: undefined,\n    throw_range: undefined,\n    two_handed_damage: undefined\n  }\n})\n\n// --- Specific Equipment Type Builders ---\n\nexport const weaponFactory = equipmentFactory.params({\n  damage: damageFactory.build(),\n  range: rangeFactory.build(),\n  properties: apiReferenceFactory.buildList(\n    2,\n    {},\n    { transient: { resource: 'weapon-properties' } }\n  ),\n  weight: faker.number.int({ min: 1, max: 20 })\n})\n\nexport const armorFactory = equipmentFactory.params({\n  weight: faker.number.int({ min: 10, max: 65 }),\n  properties: apiReferenceFactory.buildList(1, {}, { transient: { resource: 'armor-properties' } })\n})\n\nexport const thrownWeaponFactory = weaponFactory.params({\n  throw_range: throwRangeFactory.build()\n})\n"
  },
  {
    "path": "src/tests/factories/2024/equipmentCategory.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { EquipmentCategory2024 } from '@/models/2024/equipmentCategory'\n\nimport { apiReferenceFactory } from './common.factory' // Import common factory\n\nexport const equipmentCategoryFactory = Factory.define<EquipmentCategory2024>(({ sequence }) => {\n  const name = `Equipment Category ${sequence} - ${faker.lorem.words(2)}`\n  const index = name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n\n  return {\n    index: index,\n    name: name,\n    equipment: apiReferenceFactory.buildList(faker.number.int({ min: 1, max: 5 })), // Build a list of equipment references\n    url: `/api/equipment-categories/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2024/feat.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Feat2024, FeatPrerequisites2024 } from '@/models/2024/feat'\n\nconst FEAT_TYPES = ['origin', 'general', 'fighting-style', 'epic-boon'] as const\n\nexport const featPrerequisitesFactory = Factory.define<FeatPrerequisites2024>(() => ({\n  minimum_level: faker.number.int({ min: 1, max: 20 })\n}))\n\nexport const featFactory = Factory.define<Feat2024>(({ sequence }) => {\n  const name = `Feat ${sequence} - ${faker.lorem.words(2)}`\n  const index = name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n\n  return {\n    index,\n    name,\n    description: faker.lorem.paragraph(),\n    type: faker.helpers.arrayElement(FEAT_TYPES),\n    url: `/api/2024/feats/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2024/language.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Language2024 } from '@/models/2024/language'\n\nexport const languageFactory = Factory.define<Language2024>(() => {\n  const name = faker.lorem.words()\n  const index = name.toLowerCase().replace(/\\s+/g, '-')\n\n  return {\n    index,\n    name,\n    is_rare: faker.datatype.boolean(),\n    note: faker.lorem.sentence(),\n    url: `/api/languages/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2024/magicItem.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { MagicItem2024, Rarity2024 } from '@/models/2024/magicItem'\n\nimport { apiReferenceFactory, createIndex, createUrl } from './common.factory'\n\nconst RARITY_NAMES = ['Common', 'Uncommon', 'Rare', 'Very Rare', 'Legendary'] as const\n\nexport const rarity2024Factory = Factory.define<Rarity2024>(() => ({\n  name: faker.helpers.arrayElement(RARITY_NAMES)\n}))\n\nexport const magicItemFactory = Factory.define<MagicItem2024>(({ sequence, params }) => {\n  const name = params.name ?? `Magic Item ${sequence}`\n  const index = params.index ?? createIndex(name)\n\n  return {\n    index,\n    name,\n    desc: params.desc ?? faker.lorem.paragraph(),\n    image: params.image ?? `/images/magic-items/${index}.png`,\n    equipment_category: apiReferenceFactory.build(\n      params.equipment_category ?? { url: createUrl('equipment-categories', 'wondrous-items') }\n    ),\n    attunement: params.attunement ?? false,\n    variant: params.variant ?? false,\n    variants: params.variants ?? [],\n    rarity: rarity2024Factory.build(params.rarity),\n    url: params.url ?? createUrl('magic-items', index),\n    updated_at: params.updated_at ?? faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2024/magicSchool.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { MagicSchool2024 } from '@/models/2024/magicSchool'\n\nexport const magicSchoolFactory = Factory.define<MagicSchool2024>(() => {\n  const name = faker.lorem.words()\n  const index = name.toLowerCase().replace(/\\s+/g, '-')\n\n  return {\n    index,\n    name,\n    description: faker.lorem.paragraph(),\n    url: `/api/magic-schools/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2024/proficiency.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Proficiency2024 } from '@/models/2024/proficiency'\n\nimport { apiReferenceFactory } from './common.factory'\n\nexport const proficiencyFactory = Factory.define<Proficiency2024>(({ sequence }) => {\n  const name = `Proficiency ${sequence} - ${faker.lorem.words(2)}`\n  const index = name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n\n  return {\n    index,\n    name,\n    type: faker.helpers.arrayElement(['Skills', 'Tools']),\n    backgrounds: [],\n    classes: [],\n    reference: apiReferenceFactory.build(),\n    url: `/api/2024/proficiencies/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2024/skill.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Skill2024 } from '@/models/2024/skill'\n\nimport { apiReferenceFactory } from './common.factory' // Import common factory\n\nexport const skillFactory = Factory.define<Skill2024>(({ sequence }) => {\n  const name = `Skill ${sequence} - ${faker.lorem.words(2)}`\n  const index = name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n\n  return {\n    index: index,\n    name: name,\n    description: faker.lorem.paragraph(),\n    // Build a default ability score using the common factory\n    ability_score: apiReferenceFactory.build({\n      index: faker.helpers.arrayElement(['str', 'dex', 'con', 'int', 'wis', 'cha']),\n      name: faker.helpers.arrayElement(['STR', 'DEX', 'CON', 'INT', 'WIS', 'CHA']),\n      url: faker.internet.url()\n    }),\n    url: `/api/skills/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2024/species.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Species2024 } from '@/models/2024/species'\n\nimport { apiReferenceFactory, createIndex, createUrl } from './common.factory'\n\nexport const speciesFactory = Factory.define<Species2024>(({ sequence }) => {\n  const name = `Species ${sequence} - ${faker.lorem.words(2)}`\n  const index = createIndex(name)\n\n  return {\n    index,\n    name,\n    url: createUrl('species', index),\n    type: faker.helpers.arrayElement(['Humanoid', 'Construct', 'Fey']),\n    size: faker.helpers.arrayElement(['Small', 'Medium']),\n    size_options: undefined,\n    speed: faker.helpers.arrayElement([25, 30, 35]),\n    traits: apiReferenceFactory.buildList(faker.number.int({ min: 0, max: 4 }), {}, { transient: { resourceType: 'traits' } }),\n    subspecies: apiReferenceFactory.buildList(faker.number.int({ min: 0, max: 3 }), {}, { transient: { resourceType: 'subspecies' } }),\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2024/subclass.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Subclass2024, SubclassFeature2024 } from '@/models/2024/subclass'\n\nexport const subclassFeatureFactory = Factory.define<SubclassFeature2024>(() => ({\n  name: faker.lorem.words(3),\n  level: faker.number.int({ min: 1, max: 20 }),\n  description: faker.lorem.paragraph()\n}))\n\nexport const subclassFactory = Factory.define<Subclass2024>(({ sequence }) => {\n  const name = `Subclass ${sequence} - ${faker.lorem.words(2)}`\n  const index = name\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '')\n\n  return {\n    index,\n    name,\n    summary: faker.lorem.sentence(),\n    description: faker.lorem.paragraph(),\n    features: subclassFeatureFactory.buildList(2),\n    url: `/api/2024/subclasses/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2024/subspecies.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Subspecies2024, SubspeciesTrait } from '@/models/2024/subspecies'\n\nimport { apiReferenceFactory, createIndex, createUrl } from './common.factory'\n\nconst subspeciesTraitFactory = Factory.define<SubspeciesTrait>(({ sequence }) => {\n  const name = `Subspecies Trait ${sequence} - ${faker.lorem.words(2)}`\n  const index = createIndex(name)\n\n  return {\n    index,\n    name,\n    url: createUrl('traits', index),\n    level: faker.helpers.arrayElement([1, 3, 5, 7])\n  }\n})\n\nexport const subspeciesFactory = Factory.define<Subspecies2024>(({ sequence }) => {\n  const name = `Subspecies ${sequence} - ${faker.lorem.words(2)}`\n  const index = createIndex(name)\n  const speciesIndex = `species-${faker.lorem.word()}`\n\n  return {\n    index,\n    name,\n    url: createUrl('subspecies', index),\n    species: apiReferenceFactory.build({\n      index: speciesIndex,\n      name: faker.lorem.words(2),\n      url: createUrl('species', speciesIndex)\n    }),\n    traits: subspeciesTraitFactory.buildList(faker.number.int({ min: 1, max: 3 })),\n    damage_type: undefined,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2024/trait.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { Trait2024 } from '@/models/2024/trait'\n\nimport { apiReferenceFactory, createIndex, createUrl } from './common.factory'\n\nexport const traitFactory = Factory.define<Trait2024>(({ sequence }) => {\n  const name = `Trait ${sequence} - ${faker.lorem.words(2)}`\n  const index = createIndex(name)\n\n  return {\n    index,\n    name,\n    url: createUrl('traits', index),\n    description: faker.lorem.paragraph(),\n    species: [apiReferenceFactory.build({}, { transient: { resourceType: 'species' } })],\n    subspecies: [],\n    proficiency_choices: undefined,\n    speed: undefined,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2024/weaponMasteryProperty.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { WeaponMasteryProperty2024 } from '@/models/2024/weaponMasteryProperty'\n\nexport const weaponMasteryPropertyFactory = Factory.define<WeaponMasteryProperty2024>(() => {\n  const name = faker.lorem.words()\n  const index = name.toLowerCase().replace(/\\s+/g, '-')\n\n  return {\n    index,\n    name,\n    description: faker.lorem.paragraph(),\n    url: `/api/weapon-mastery-properties/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/factories/2024/weaponProperty.factory.ts",
    "content": "import { faker } from '@faker-js/faker'\nimport { Factory } from 'fishery'\n\nimport { WeaponProperty2024 } from '@/models/2024/weaponProperty'\n\nexport const weaponPropertyFactory = Factory.define<WeaponProperty2024>(() => {\n  const name = faker.lorem.words()\n  const index = name.toLowerCase().replace(/\\s+/g, '-')\n\n  return {\n    index,\n    name,\n    description: faker.lorem.paragraph(),\n    url: `/api/weapon-properties/${index}`,\n    updated_at: faker.date.recent().toISOString()\n  }\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/abilityScores.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/ability-scores', () => {\n  it('should list ability scores', async () => {\n    const res = await request(app).get('/api/2014/ability-scores')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/ability-scores')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/ability-scores?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/ability-scores')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/ability-scores?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2014/ability-scores/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/ability-scores')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/ability-scores/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/ability-scores/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/classes.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/classes', () => {\n  it('should list classes', async () => {\n    const res = await request(app).get('/api/2014/classes')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/classes')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/classes?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/classes')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/classes?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2014/classes/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/classes')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/classes/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/classes/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n\n    describe('/api/2014/classes/:index/subclasses', () => {\n      it('returns objects', async () => {\n        const indexRes = await request(app).get('/api/2014/classes')\n        const index = indexRes.body.results[1].index\n        const res = await request(app).get(`/api/2014/classes/${index}/subclasses`)\n        expect(res.statusCode).toEqual(200)\n        expect(res.body.results.length).not.toEqual(0)\n      })\n    })\n\n    describe('/api/2014/classes/:index/starting-equipment', () => {\n      it('returns objects', async () => {\n        const indexRes = await request(app).get('/api/2014/classes')\n        const index = indexRes.body.results[1].index\n        const res = await request(app).get(`/api/2014/classes/${index}/starting-equipment`)\n        expect(res.statusCode).toEqual(200)\n      })\n    })\n\n    describe('/api/2014/classes/:index/spellcasting', () => {\n      it('returns objects', async () => {\n        const indexRes = await request(app).get('/api/2014/classes')\n        const index = indexRes.body.results[1].index\n        const res = await request(app).get(`/api/2014/classes/${index}/spellcasting`)\n        expect(res.statusCode).toEqual(200)\n      })\n    })\n\n    describe('/api/2014/classes/:index/spells', () => {\n      it('returns objects', async () => {\n        const res = await request(app).get('/api/2014/classes/wizard/spells')\n        expect(res.statusCode).toEqual(200)\n        expect(res.body.results.length).not.toEqual(0)\n      })\n    })\n\n    describe('/api/2014/classes/:index/features', () => {\n      it('returns objects', async () => {\n        const indexRes = await request(app).get('/api/2014/classes')\n        const index = indexRes.body.results[1].index\n        const res = await request(app).get(`/api/2014/classes/${index}/features`)\n        expect(res.statusCode).toEqual(200)\n        expect(res.body.results.length).not.toEqual(0)\n      })\n    })\n\n    describe('/api/2014/classes/:index/proficiencies', () => {\n      it('returns objects', async () => {\n        const indexRes = await request(app).get('/api/2014/classes')\n        const index = indexRes.body.results[1].index\n        const res = await request(app).get(`/api/2014/classes/${index}/proficiencies`)\n        expect(res.statusCode).toEqual(200)\n        expect(res.body.results.length).not.toEqual(0)\n      })\n    })\n\n    describe('/api/2014/classes/:index/multi-classing', () => {\n      it('returns objects', async () => {\n        const indexRes = await request(app).get('/api/2014/classes')\n        const index = indexRes.body.results[1].index\n        const res = await request(app).get(`/api/2014/classes/${index}/multi-classing`)\n        expect(res.statusCode).toEqual(200)\n      })\n    })\n\n    describe('/api/2014/classes/:index/levels', () => {\n      it('returns objects', async () => {\n        const indexRes = await request(app).get('/api/2014/classes')\n        const index = indexRes.body.results[1].index\n        const res = await request(app).get(`/api/2014/classes/${index}/levels`)\n        expect(res.statusCode).toEqual(200)\n        expect(res.body.length).not.toEqual(0)\n        expect(res.body.length).toEqual(20)\n      })\n\n      it('returns the subclass levels as well', async () => {\n        const indexRes = await request(app).get('/api/2014/classes')\n        const index = indexRes.body.results[1].index\n        const classRes = await request(app).get(`/api/2014/classes/${index}`)\n        const subclass = classRes.body.subclasses[0].index\n        const res = await request(app).get(`/api/2014/classes/${index}/levels?subclass=${subclass}`)\n        expect(res.statusCode).toEqual(200)\n        expect(res.body.length).not.toEqual(0)\n        expect(res.body.length).toBeGreaterThan(20)\n      })\n\n      describe('/api/2014/classes/:index/levels/:level', () => {\n        it('returns objects', async () => {\n          const indexRes = await request(app).get('/api/2014/classes')\n          const index = indexRes.body.results[1].index\n          const level = 1\n          const res = await request(app).get(`/api/2014/classes/${index}/levels/${level}`)\n          expect(res.statusCode).toEqual(200)\n          expect(res.body.level).toEqual(level)\n        })\n      })\n\n      describe('/api/2014/classes/:index/levels/:level/spells', () => {\n        it('returns objects', async () => {\n          const index = 'wizard'\n          const level = 1\n          const res = await request(app).get(`/api/2014/classes/${index}/levels/${level}/spells`)\n          expect(res.statusCode).toEqual(200)\n          expect(res.body.results.length).not.toEqual(0)\n        })\n      })\n\n      describe('/api/2014/classes/:index/levels/:level/features', () => {\n        it('returns objects', async () => {\n          const indexRes = await request(app).get('/api/2014/classes')\n          const index = indexRes.body.results[1].index\n          const level = 1\n          const res = await request(app).get(`/api/2014/classes/${index}/levels/${level}/spells`)\n          expect(res.statusCode).toEqual(200)\n          expect(res.body.results.length).not.toEqual(0)\n        })\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/conditions.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/conditions', () => {\n  it('should list conditions', async () => {\n    const res = await request(app).get('/api/2014/conditions')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/conditions')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/conditions?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/conditions')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/conditions?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2014/conditions/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/conditions')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/conditions/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/conditions/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/damageTypes.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/damage-types', () => {\n  it('should list damage types', async () => {\n    const res = await request(app).get('/api/2014/damage-types')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/damage-types')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/damage-types?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/damage-types')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/damage-types?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2014/damage-types/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/damage-types')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/damage-types/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/damage-types/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/equipment.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/equipment', () => {\n  it('should list equipment', async () => {\n    const res = await request(app).get('/api/2014/equipment')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/equipment')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/equipment?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/equipment')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/equipment?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2014/equipment/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/equipment')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/equipment/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/equipment/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/equipmentCategories.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/equipment-categories', () => {\n  it('should list equipment categories', async () => {\n    const res = await request(app).get('/api/2014/equipment-categories')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/equipment-categories')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/equipment-categories?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/equipment-categories')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/equipment-categories?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2014/equipment-categories/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/equipment-categories')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/equipment-categories/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/equipment-categories/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/feats.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/feats', () => {\n  it('should list feats', async () => {\n    const res = await request(app).get('/api/2014/feats')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/feats')\n      const name = indexRes.body.results[0].name\n      const res = await request(app).get(`/api/2014/feats?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/feats')\n      const name = indexRes.body.results[0].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/feats?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2014/feats/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/feats')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/feats/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/feats/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/features.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/features', () => {\n  it('should list features', async () => {\n    const res = await request(app).get('/api/2014/features')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/features')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/features?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/features')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/features?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2014/features/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/features')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/features/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/features/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/languages.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/languages', () => {\n  it('should list languages', async () => {\n    const res = await request(app).get('/api/2014/languages')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/languages')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/languages?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/languages')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/languages?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2014/languages/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/languages')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/languages/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/languages/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/magicItems.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/magic-items', () => {\n  it('should list magic items', async () => {\n    const res = await request(app).get('/api/2014/magic-items')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  it('should hit the cache', async () => {\n    await redisClient.del('/api/2014/magic-items')\n    const clientSet = vi.spyOn(redisClient, 'set')\n    await request(app).get('/api/2014/magic-items')\n    const res = await request(app).get('/api/2014/magic-items')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n    expect(clientSet).toHaveBeenCalledTimes(1)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/magic-items')\n      const name = indexRes.body.results[5].name\n      const res = await request(app).get(`/api/2014/magic-items?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/magic-items')\n      const name = indexRes.body.results[5].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/magic-items?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2014/magic-items/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/magic-items')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/magic-items/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/magic-items/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/magicSchools.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/magic-schools', () => {\n  it('should list magic items', async () => {\n    const res = await request(app).get('/api/2014/magic-schools')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/magic-schools')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/magic-schools?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/magic-schools')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/magic-schools?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2014/magic-schools/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/magic-schools')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/magic-schools/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/magic-schools/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/monsters.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/monsters', () => {\n  it('should list monsters', async () => {\n    const res = await request(app).get('/api/2014/monsters')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  it('should hit the cache', async () => {\n    await redisClient.del('/api/2014/monsters')\n    const clientSet = vi.spyOn(redisClient, 'set')\n    await request(app).get('/api/2014/monsters')\n    const res = await request(app).get('/api/2014/monsters')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n    expect(clientSet).toHaveBeenCalledTimes(1)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/monsters')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/monsters?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/monsters')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/monsters?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('with challenge_rating query', () => {\n    describe('with only one provided challenge rating', () => {\n      it('returns expected objects', async () => {\n        const expectedCR = 0.25\n        const res = await request(app).get(`/api/2014/monsters?challenge_rating=${expectedCR}`)\n        expect(res.statusCode).toEqual(200)\n\n        const randomIndex = Math.floor(Math.random() * res.body.results.length)\n        const randomResult = res.body.results[randomIndex]\n\n        const indexRes = await request(app).get(`/api/2014/monsters/${randomResult.index}`)\n        expect(indexRes.statusCode).toEqual(200)\n        expect(indexRes.body.challenge_rating).toEqual(expectedCR)\n      })\n    })\n\n    describe('with many provided challenge ratings', () => {\n      it('returns expected objects', async () => {\n        const cr1 = 1\n        const cr1Res = await request(app).get(`/api/2014/monsters?challenge_rating=${cr1}`)\n        expect(cr1Res.statusCode).toEqual(200)\n\n        const cr20 = 20\n        const cr20Res = await request(app).get(`/api/2014/monsters?challenge_rating=${cr20}`)\n        expect(cr20Res.statusCode).toEqual(200)\n\n        const bothRes = await request(app).get(\n          `/api/2014/monsters?challenge_rating=${cr1}&challenge_rating=${cr20}`\n        )\n        expect(bothRes.statusCode).toEqual(200)\n        expect(bothRes.body.count).toEqual(cr1Res.body.count + cr20Res.body.count)\n\n        const altBothRes = await request(app).get(\n          `/api/2014/monsters?challenge_rating=${cr1},${cr20}`\n        )\n        expect(altBothRes.statusCode).toEqual(200)\n        expect(altBothRes.body.count).toEqual(cr1Res.body.count + cr20Res.body.count)\n\n        const randomIndex = Math.floor(Math.random() * bothRes.body.results.length)\n        const randomResult = bothRes.body.results[randomIndex]\n\n        const indexRes = await request(app).get(`/api/2014/monsters/${randomResult.index}`)\n        expect(indexRes.statusCode).toEqual(200)\n        expect(\n          indexRes.body.challenge_rating == cr1 || indexRes.body.challenge_rating == cr20\n        ).toBeTruthy()\n      })\n    })\n  })\n\n  describe('/api/2014/monsters/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/monsters')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/monsters/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/monsters/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/proficiencies.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/proficiencies', () => {\n  it('should list proficiencies', async () => {\n    const res = await request(app).get('/api/2014/proficiencies')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/proficiencies')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/proficiencies?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/proficiencies')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/proficiencies?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2014/proficiencies/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/proficiencies')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/proficiencies/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/proficiencies/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/races.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/races', () => {\n  it('should list races', async () => {\n    const res = await request(app).get('/api/2014/races')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/races')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/races?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/races')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/races?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2014/races/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/races')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/races/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/races/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n\n    describe('/api/2014/races/:index/subraces', () => {\n      it('returns objects', async () => {\n        const indexRes = await request(app).get('/api/2014/races')\n        const index = indexRes.body.results[1].index\n        const res = await request(app).get(`/api/2014/races/${index}/subraces`)\n        expect(res.statusCode).toEqual(200)\n        expect(res.body.results.length).not.toEqual(0)\n      })\n    })\n\n    describe('/api/2014/races/:index/proficiencies', () => {\n      it('returns objects', async () => {\n        const indexRes = await request(app).get('/api/2014/races')\n        const index = indexRes.body.results[1].index\n        const res = await request(app).get(`/api/2014/races/${index}/proficiencies`)\n        expect(res.statusCode).toEqual(200)\n        expect(res.body.results.length).not.toEqual(0)\n      })\n    })\n\n    describe('/api/2014/races/:index/traits', () => {\n      it('returns objects', async () => {\n        const indexRes = await request(app).get('/api/2014/races')\n        const index = indexRes.body.results[1].index\n        const res = await request(app).get(`/api/2014/races/${index}/traits`)\n        expect(res.statusCode).toEqual(200)\n        expect(res.body.results.length).not.toEqual(0)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/ruleSections.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/rule-sections', () => {\n  it('should list rule sections', async () => {\n    const res = await request(app).get('/api/2014/rule-sections')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  it('should hit the cache', async () => {\n    await redisClient.del('/api/2014/rule-sections')\n    const clientSet = vi.spyOn(redisClient, 'set')\n    await request(app).get('/api/2014/rule-sections')\n    const res = await request(app).get('/api/2014/rule-sections')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n    expect(clientSet).toHaveBeenCalledTimes(1)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/rule-sections')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/rule-sections?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/rule-sections')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/rule-sections?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('with desc query', () => {\n    it('returns the object with matching desc', async () => {\n      const indexRes = await request(app).get('/api/2014/rule-sections')\n      const index = indexRes.body.results[1].index\n      const res = await request(app).get(`/api/2014/rule-sections/${index}`)\n      const name = res.body.name\n      const descRes = await request(app).get(`/api/2014/rule-sections?desc=${name}`)\n      expect(descRes.statusCode).toEqual(200)\n      expect(descRes.body.results[0].index).toEqual(index)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/rule-sections')\n      const index = indexRes.body.results[1].index\n      const name = indexRes.body.results[1].name\n      const queryDesc = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/rule-sections?desc=${queryDesc}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].index).toEqual(index)\n    })\n  })\n})\n\ndescribe('/api/2014/rule-sections/:index', () => {\n  it('should return one object', async () => {\n    const indexRes = await request(app).get('/api/2014/rule-sections')\n    const index = indexRes.body.results[0].index\n    const showRes = await request(app).get(`/api/2014/rule-sections/${index}`)\n    expect(showRes.statusCode).toEqual(200)\n    expect(showRes.body.index).toEqual(index)\n  })\n\n  describe('with an invalid index', () => {\n    it('should return 404', async () => {\n      const invalidIndex = 'invalid-index'\n      const showRes = await request(app).get(`/api/2014/rule-sections/${invalidIndex}`)\n      expect(showRes.statusCode).toEqual(404)\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/rules.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/rules', () => {\n  it('should list rules', async () => {\n    const res = await request(app).get('/api/2014/rules')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  it('should hit the cache', async () => {\n    await redisClient.del('/api/2014/rules')\n    const clientSet = vi.spyOn(redisClient, 'set')\n    await request(app).get('/api/2014/rules')\n    const res = await request(app).get('/api/2014/rules')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n    expect(clientSet).toHaveBeenCalledTimes(1)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/rules')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/rules?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/rules')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/rules?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('with desc query', () => {\n    it('returns the object with matching desc', async () => {\n      const indexRes = await request(app).get('/api/2014/rules')\n      const index = indexRes.body.results[1].index\n      const res = await request(app).get(`/api/2014/rules/${index}`)\n      const name = res.body.name\n      const descRes = await request(app).get(`/api/2014/rules?desc=${name}`)\n      expect(descRes.statusCode).toEqual(200)\n      expect(descRes.body.results[0].index).toEqual(index)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/rules')\n      const index = indexRes.body.results[1].index\n      const name = indexRes.body.results[1].name\n      const queryDesc = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/rules?desc=${queryDesc}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].index).toEqual(index)\n    })\n  })\n\n  describe('/api/2014/rules/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/rules')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/rules/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/rules/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/skills.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/skills', () => {\n  it('should list skills', async () => {\n    const res = await request(app).get('/api/2014/skills')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/skills')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/skills?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/skills')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/skills?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2014/skills/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/skills')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/skills/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/skills/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/spells.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/spells', () => {\n  it('should list spells', async () => {\n    const res = await request(app).get('/api/2014/spells')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  it('should hit the cache', async () => {\n    await redisClient.del('/api/2014/spells')\n    const clientSet = vi.spyOn(redisClient, 'set')\n    await request(app).get('/api/2014/spells')\n    const res = await request(app).get('/api/2014/spells')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n    expect(clientSet).toHaveBeenCalledTimes(1)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/spells')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/spells?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/spells')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/spells?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('with level query', () => {\n    it('returns expected objects', async () => {\n      const expectedLevel = 2\n      const res = await request(app).get(`/api/2014/spells?level=${expectedLevel}`)\n      expect(res.statusCode).toEqual(200)\n\n      const randomIndex = Math.floor(Math.random() * res.body.results.length)\n      const randomResult = res.body.results[randomIndex]\n\n      const indexRes = await request(app).get(`/api/2014/spells/${randomResult.index}`)\n      expect(indexRes.statusCode).toEqual(200)\n      expect(indexRes.body.level).toEqual(expectedLevel)\n    })\n\n    describe('with many provided level', () => {\n      it('returns expected objects', async () => {\n        const expectedLevel1 = 1\n        const res1 = await request(app).get(`/api/2014/spells?level=${expectedLevel1}`)\n        expect(res1.statusCode).toEqual(200)\n\n        const expectLevel2 = 8\n        const res2 = await request(app).get(`/api/2014/spells?level=${expectLevel2}`)\n        expect(res2.statusCode).toEqual(200)\n\n        const bothRes = await request(app).get(\n          `/api/2014/spells?level=${expectedLevel1}&level=${expectLevel2}`\n        )\n        expect(bothRes.statusCode).toEqual(200)\n        expect(bothRes.body.count).toEqual(res1.body.count + res2.body.count)\n\n        const randomIndex = Math.floor(Math.random() * bothRes.body.results.length)\n        const randomResult = bothRes.body.results[randomIndex]\n\n        const indexRes = await request(app).get(`/api/2014/spells/${randomResult.index}`)\n        expect(indexRes.statusCode).toEqual(200)\n        expect(\n          indexRes.body.level == expectedLevel1 || indexRes.body.level == expectLevel2\n        ).toBeTruthy()\n      })\n    })\n  })\n\n  describe('with school query', () => {\n    it('returns expected objects', async () => {\n      const expectedSchool = 'Illusion'\n      const res = await request(app).get(`/api/2014/spells?school=${expectedSchool}`)\n      expect(res.statusCode).toEqual(200)\n\n      const randomIndex = Math.floor(Math.random() * res.body.results.length)\n      const randomResult = res.body.results[randomIndex]\n\n      const indexRes = await request(app).get(`/api/2014/spells/${randomResult.index}`)\n      expect(indexRes.statusCode).toEqual(200)\n      expect(indexRes.body.school.name).toEqual(expectedSchool)\n    })\n\n    describe('with many provided schools', () => {\n      it('returns expected objects', async () => {\n        const expectedSchool1 = 'Illusion'\n        const res1 = await request(app).get(`/api/2014/spells?school=${expectedSchool1}`)\n        expect(res1.statusCode).toEqual(200)\n\n        const expectedSchool2 = 'Evocation'\n        const res2 = await request(app).get(`/api/2014/spells?school=${expectedSchool2}`)\n        expect(res2.statusCode).toEqual(200)\n\n        const bothRes = await request(app).get(\n          `/api/2014/spells?school=${expectedSchool1}&school=${expectedSchool2}`\n        )\n        expect(bothRes.statusCode).toEqual(200)\n        expect(bothRes.body.count).toEqual(res1.body.count + res2.body.count)\n\n        const randomIndex = Math.floor(Math.random() * bothRes.body.results.length)\n        const randomResult = bothRes.body.results[randomIndex]\n\n        const indexRes = await request(app).get(`/api/2014/spells/${randomResult.index}`)\n        expect(indexRes.statusCode).toEqual(200)\n        expect(\n          indexRes.body.school.name == expectedSchool1 ||\n            indexRes.body.school.name == expectedSchool2\n        ).toBeTruthy()\n      })\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/spells')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/spells/${index}`)\n      const school = showRes.body.school.name\n      const querySchool = school.toLowerCase()\n      const res = await request(app).get(`/api/2014/spells?school=${querySchool}`)\n\n      const randomIndex = Math.floor(Math.random() * res.body.results.length)\n      const randomResult = res.body.results[randomIndex]\n\n      const queryRes = await request(app).get(`/api/2014/spells/${randomResult.index}`)\n      expect(queryRes.statusCode).toEqual(200)\n      expect(queryRes.body.school.name).toEqual(school)\n    })\n  })\n\n  describe('/api/2014/spells/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/spells')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/spells/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/spells/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/subclasses.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/subclasses', () => {\n  it('should list subclasses', async () => {\n    const res = await request(app).get('/api/2014/subclasses')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/subclasses')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/subclasses?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/subclasses')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/subclasses?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2014/subclasses/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/subclasses')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/subclasses/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/subclasses/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n\n    describe('/api/2014/subclasses/:index/levels', () => {\n      it('returns objects', async () => {\n        const index = 'berserker'\n        const res = await request(app).get(`/api/2014/subclasses/${index}/levels`)\n        expect(res.statusCode).toEqual(200)\n        expect(res.body.length).not.toEqual(0)\n      })\n\n      describe('/api/2014/subclasses/:index/levels/:level', () => {\n        it('returns objects', async () => {\n          const index = 'berserker'\n          const level = 3\n          const res = await request(app).get(`/api/2014/subclasses/${index}/levels/${level}`)\n          expect(res.statusCode).toEqual(200)\n          expect(res.body.level).toEqual(level)\n        })\n\n        describe('/api/2014/subclasses/:index/levels/:level/features', () => {\n          it('returns objects', async () => {\n            const index = 'berserker'\n            const level = 3\n            const res = await request(app).get(\n              `/api/2014/subclasses/${index}/levels/${level}/features`\n            )\n            expect(res.statusCode).toEqual(200)\n            expect(res.body.results.length).not.toEqual(0)\n          })\n        })\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/subraces.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/subraces', () => {\n  it('should list subraces', async () => {\n    const res = await request(app).get('/api/2014/subraces')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/subraces')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/subraces?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/subraces')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/subraces?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2014/subraces/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/subraces')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/subraces/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/subraces/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n\n    describe('/api/2014/subraces/:index/traits', () => {\n      it('returns objects', async () => {\n        const indexRes = await request(app).get('/api/2014/subraces')\n        const index = indexRes.body.results[1].index\n        const res = await request(app).get(`/api/2014/subraces/${index}/traits`)\n        expect(res.statusCode).toEqual(200)\n        expect(res.body.results.length).not.toEqual(0)\n      })\n    })\n\n    describe('/api/2014/subraces/:index/proficiencies', () => {\n      it('returns objects', async () => {\n        const res = await request(app).get('/api/2014/subraces/high-elf/proficiencies')\n        expect(res.statusCode).toEqual(200)\n        expect(res.body.results.length).not.toEqual(0)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/traits.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/traits', () => {\n  it('should list traits', async () => {\n    const res = await request(app).get('/api/2014/traits')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/traits')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/traits?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/traits')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/traits?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2014/traits/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/traits')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/traits/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/traits/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2014/weaponProperties.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2014/weapon-properties', () => {\n  it('should list weapon properties', async () => {\n    const res = await request(app).get('/api/2014/weapon-properties')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2014/weapon-properties')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2014/weapon-properties?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2014/weapon-properties')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2014/weapon-properties?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2014/weapon-properties/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2014/weapon-properties')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2014/weapon-properties/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2014/weapon-properties/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/abilityScores.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/ability-scores', () => {\n  it('should list ability scores', async () => {\n    const res = await request(app).get('/api/2024/ability-scores')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/ability-scores')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2024/ability-scores?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/ability-scores')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2024/ability-scores?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/ability-scores/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/ability-scores')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/ability-scores/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2024/ability-scores/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/alignment.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen()\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/alignments', () => {\n  it('should list alignments', async () => {\n    const res = await request(app).get('/api/2024/alignments')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/alignments')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2024/alignments?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/alignments')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2024/alignments?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/alignments/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/alignments')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/alignments/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2024/alignments/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/background.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen()\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/backgrounds', () => {\n  it('should list backgrounds', async () => {\n    const res = await request(app).get('/api/2024/backgrounds')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/backgrounds')\n      const name = indexRes.body.results[0].name\n      const res = await request(app).get(`/api/2024/backgrounds?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/backgrounds')\n      const name = indexRes.body.results[0].name\n      const res = await request(app).get(`/api/2024/backgrounds?name=${name.toLowerCase()}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/backgrounds/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/backgrounds')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/backgrounds/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const showRes = await request(app).get('/api/2024/backgrounds/invalid-index')\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/condition.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen()\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/conditions', () => {\n  it('should list conditions', async () => {\n    const res = await request(app).get('/api/2024/conditions')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/conditions')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2024/conditions?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/conditions')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2024/conditions?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/conditions/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/conditions')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/conditions/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2024/conditions/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/damageType.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen()\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/damage-types', () => {\n  it('should list damage types', async () => {\n    const res = await request(app).get('/api/2024/damage-types')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/damage-types')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2024/damage-types?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/damage-types')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2024/damage-types?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/damage-types/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/damage-types')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/damage-types/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2024/damage-types/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/equipment.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/equipment', () => {\n  it('should list equipment', async () => {\n    const res = await request(app).get('/api/2024/equipment')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/equipment')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2024/equipment?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/equipment')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2024/equipment?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/equipment/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/equipment')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/equipment/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2024/equipment/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/equipmentCategories.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/equipment-categories', () => {\n  it('should list equipment categories', async () => {\n    const res = await request(app).get('/api/2024/equipment-categories')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/equipment-categories')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2024/equipment-categories?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/equipment-categories')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2024/equipment-categories?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/equipment-categories/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/equipment-categories')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/equipment-categories/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2024/equipment-categories/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/feat.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen()\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/feats', () => {\n  it('should list feats', async () => {\n    const res = await request(app).get('/api/2024/feats')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/feats')\n      const name = indexRes.body.results[0].name\n      const res = await request(app).get(`/api/2024/feats?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/feats')\n      const name = indexRes.body.results[0].name\n      const res = await request(app).get(`/api/2024/feats?name=${name.toLowerCase()}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/feats/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/feats')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/feats/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const showRes = await request(app).get('/api/2024/feats/invalid-index')\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/language.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen()\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/languages', () => {\n  it('should list languages', async () => {\n    const res = await request(app).get('/api/2024/languages')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/languages')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2024/languages?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/languages')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2024/languages?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/languages/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/languages')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/languages/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2024/languages/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/magicItem.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen()\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/magic-items', () => {\n  it('should list magic items', async () => {\n    const res = await request(app).get('/api/2024/magic-items')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/magic-items')\n      const name = indexRes.body.results[0].name\n      const res = await request(app).get(`/api/2024/magic-items?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/magic-items')\n      const name = indexRes.body.results[0].name\n      const res = await request(app).get(`/api/2024/magic-items?name=${name.toLowerCase()}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/magic-items/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/magic-items')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/magic-items/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const showRes = await request(app).get('/api/2024/magic-items/invalid-index')\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/magicSchool.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen()\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/magic-schools', () => {\n  it('should list magic schools', async () => {\n    const res = await request(app).get('/api/2024/magic-schools')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/magic-schools')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2024/magic-schools?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/magic-schools')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2024/magic-schools?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/magic-schools/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/magic-schools')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/magic-schools/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2024/magic-schools/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/proficiency.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen()\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/proficiencies', () => {\n  it('should list proficiencies', async () => {\n    const res = await request(app).get('/api/2024/proficiencies')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/proficiencies')\n      const name = indexRes.body.results[0].name\n      const res = await request(app).get(`/api/2024/proficiencies?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/proficiencies')\n      const name = indexRes.body.results[0].name\n      const res = await request(app).get(`/api/2024/proficiencies?name=${name.toLowerCase()}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/proficiencies/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/proficiencies')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/proficiencies/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const showRes = await request(app).get('/api/2024/proficiencies/invalid-index')\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/skills.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/skills', () => {\n  it('should list skills', async () => {\n    const res = await request(app).get('/api/2024/skills')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/skills')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2024/skills?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/skills')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2024/skills?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/skills/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/skills')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/skills/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2024/skills/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/species.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen()\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/species', () => {\n  it('should list species', async () => {\n    const res = await request(app).get('/api/2024/species')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/species')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2024/species?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/species')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2024/species?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/species/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/species')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/species/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const showRes = await request(app).get('/api/2024/species/invalid-index')\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n\n  describe('/api/2024/species/:index/subspecies', () => {\n    it('should return a list of subspecies for a species that has them', async () => {\n      // Dragonborn has subspecies in the 2024 data\n      const res = await request(app).get('/api/2024/species/dragonborn/subspecies')\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results).toBeDefined()\n    })\n  })\n\n  describe('/api/2024/species/:index/traits', () => {\n    it('should return a list of traits for a species', async () => {\n      const indexRes = await request(app).get('/api/2024/species')\n      const index = indexRes.body.results[0].index\n      const res = await request(app).get(`/api/2024/species/${index}/traits`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results).toBeDefined()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/subclass.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen()\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/subclasses', () => {\n  it('should list subclasses', async () => {\n    const res = await request(app).get('/api/2024/subclasses')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/subclasses')\n      const name = indexRes.body.results[0].name\n      const res = await request(app).get(`/api/2024/subclasses?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/subclasses')\n      const name = indexRes.body.results[0].name\n      const res = await request(app).get(`/api/2024/subclasses?name=${name.toLowerCase()}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/subclasses/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/subclasses')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/subclasses/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const showRes = await request(app).get('/api/2024/subclasses/invalid-index')\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/subspecies.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen()\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/subspecies', () => {\n  it('should list subspecies', async () => {\n    const res = await request(app).get('/api/2024/subspecies')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/subspecies')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2024/subspecies?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/subspecies')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2024/subspecies?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/subspecies/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/subspecies')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/subspecies/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const showRes = await request(app).get('/api/2024/subspecies/invalid-index')\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n\n  describe('/api/2024/subspecies/:index/traits', () => {\n    it('should return a list of traits for a subspecies', async () => {\n      const indexRes = await request(app).get('/api/2024/subspecies')\n      const index = indexRes.body.results[0].index\n      const res = await request(app).get(`/api/2024/subspecies/${index}/traits`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results).toBeDefined()\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/traits.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen()\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/traits', () => {\n  it('should list traits', async () => {\n    const res = await request(app).get('/api/2024/traits')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/traits')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2024/traits?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/traits')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2024/traits?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/traits/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/traits')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/traits/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const showRes = await request(app).get('/api/2024/traits/invalid-index')\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/weaponMasteryProperty.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen()\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/weapon-mastery-properties', () => {\n  it('should list weapon mastery properties', async () => {\n    const res = await request(app).get('/api/2024/weapon-mastery-properties')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/weapon-mastery-properties')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2024/weapon-mastery-properties?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/weapon-mastery-properties')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2024/weapon-mastery-properties?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/weapon-mastery-properties/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/weapon-mastery-properties')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/weapon-mastery-properties/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(\n          `/api/2024/weapon-mastery-properties/${invalidIndex}`\n        )\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/2024/weaponProperty.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen()\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/2024/weapon-properties', () => {\n  it('should list weapon properties', async () => {\n    const res = await request(app).get('/api/2024/weapon-properties')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body.results.length).not.toEqual(0)\n  })\n\n  describe('with name query', () => {\n    it('returns the named object', async () => {\n      const indexRes = await request(app).get('/api/2024/weapon-properties')\n      const name = indexRes.body.results[1].name\n      const res = await request(app).get(`/api/2024/weapon-properties?name=${name}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n\n    it('is case insensitive', async () => {\n      const indexRes = await request(app).get('/api/2024/weapon-properties')\n      const name = indexRes.body.results[1].name\n      const queryName = name.toLowerCase()\n      const res = await request(app).get(`/api/2024/weapon-properties?name=${queryName}`)\n      expect(res.statusCode).toEqual(200)\n      expect(res.body.results[0].name).toEqual(name)\n    })\n  })\n\n  describe('/api/2024/weapon-properties/:index', () => {\n    it('should return one object', async () => {\n      const indexRes = await request(app).get('/api/2024/weapon-properties')\n      const index = indexRes.body.results[0].index\n      const showRes = await request(app).get(`/api/2024/weapon-properties/${index}`)\n      expect(showRes.statusCode).toEqual(200)\n      expect(showRes.body.index).toEqual(index)\n    })\n\n    describe('with an invalid index', () => {\n      it('should return 404', async () => {\n        const invalidIndex = 'invalid-index'\n        const showRes = await request(app).get(`/api/2024/weapon-properties/${invalidIndex}`)\n        expect(showRes.statusCode).toEqual(404)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/abilityScores.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/ability-scores', () => {\n  it('redirects to /api/2014/ability-scores', async () => {\n    await request(app)\n      .get('/api/ability-scores')\n      .expect(301)\n      .expect('Location', '/api/2014/ability-scores')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'CHA'\n    await request(app)\n      .get(`/api/ability-scores?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/ability-scores?name=${name}`)\n  })\n\n  it('redirects to /api/2014/ability-scores/{index}', async () => {\n    const index = 'strength'\n    await request(app)\n      .get(`/api/ability-scores/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/ability-scores/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/classes.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/classes', () => {\n  it('redirects to /api/2014/classes', async () => {\n    await request(app).get('/api/classes').expect(301).expect('Location', '/api/2014/classes')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Cleric'\n    await request(app)\n      .get(`/api/classes?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/classes?name=${name}`)\n  })\n\n  it('redirects to /api/2014/classes/{index}', async () => {\n    const index = 'bard'\n    await request(app)\n      .get(`/api/classes/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/classes/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/conditions.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/conditions', () => {\n  it('redirects to /api/2014/conditions', async () => {\n    await request(app).get('/api/conditions').expect(301).expect('Location', '/api/2014/conditions')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Blinded'\n    await request(app)\n      .get(`/api/conditions?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/conditions?name=${name}`)\n  })\n\n  it('redirects to /api/2014/conditions/{index}', async () => {\n    const index = 'charmed'\n    await request(app)\n      .get(`/api/conditions/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/conditions/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/damageTypes.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/damage-types', () => {\n  it('redirects to /api/2014/damage-types', async () => {\n    await request(app)\n      .get('/api/damage-types')\n      .expect(301)\n      .expect('Location', '/api/2014/damage-types')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Acid'\n    await request(app)\n      .get(`/api/damage-types?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/damage-types?name=${name}`)\n  })\n\n  it('redirects to /api/2014/damage-types/{index}', async () => {\n    const index = 'cold'\n    await request(app)\n      .get(`/api/damage-types/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/damage-types/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/equipment.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/equipment', () => {\n  it('redirects to /api/2014/equipment', async () => {\n    await request(app).get('/api/equipment').expect(301).expect('Location', '/api/2014/equipment')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Abacus'\n    await request(app)\n      .get(`/api/equipment?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/equipment?name=${name}`)\n  })\n\n  it('redirects to /api/2014/equipment/{index}', async () => {\n    const index = 'acid-vial'\n    await request(app)\n      .get(`/api/equipment/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/equipment/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/equipmentCategories.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/equipment-categories', () => {\n  it('redirects to /api/2014/equipment-categories', async () => {\n    await request(app)\n      .get('/api/equipment-categories')\n      .expect(301)\n      .expect('Location', '/api/2014/equipment-categories')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Adventuring%20Gear'\n    await request(app)\n      .get(`/api/equipment-categories?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/equipment-categories?name=${name}`)\n  })\n\n  it('redirects to /api/2014/equipment-categories/{index}', async () => {\n    const index = 'ammunition'\n    await request(app)\n      .get(`/api/equipment-categories/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/equipment-categories/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/feats.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/feats', () => {\n  it('redirects to /api/2014/feats', async () => {\n    await request(app).get('/api/feats').expect(301).expect('Location', '/api/2014/feats')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Grappler'\n    await request(app)\n      .get(`/api/feats?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/feats?name=${name}`)\n  })\n\n  it('redirects to /api/2014/feats/{index}', async () => {\n    const index = 'grappler'\n    await request(app)\n      .get(`/api/feats/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/feats/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/features.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/features', () => {\n  it('redirects to /api/2014/features', async () => {\n    await request(app).get('/api/features').expect(301).expect('Location', '/api/2014/features')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Action%20Surge'\n    await request(app)\n      .get(`/api/features?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/features?name=${name}`)\n  })\n\n  it('redirects to /api/2014/features/{index}', async () => {\n    const index = 'arcane-recovery'\n    await request(app)\n      .get(`/api/features/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/features/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/languages.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/languages', () => {\n  it('redirects to /api/2014/languages', async () => {\n    await request(app).get('/api/languages').expect(301).expect('Location', '/api/2014/languages')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Abyssal'\n    await request(app)\n      .get(`/api/languages?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/languages?name=${name}`)\n  })\n\n  it('redirects to /api/2014/languages/{index}', async () => {\n    const index = 'celestial'\n    await request(app)\n      .get(`/api/languages/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/languages/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/magicItems.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/magic-items', () => {\n  it('redirects to /api/2014/magic-items', async () => {\n    await request(app)\n      .get('/api/magic-items')\n      .expect(301)\n      .expect('Location', '/api/2014/magic-items')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Adamantine%20Armor'\n    await request(app)\n      .get(`/api/magic-items?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/magic-items?name=${name}`)\n  })\n\n  it('redirects to /api/2014/magic-items/{index}', async () => {\n    const index = 'ammunition'\n    await request(app)\n      .get(`/api/magic-items/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/magic-items/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/magicSchools.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/magic-schools', () => {\n  it('redirects to /api/2014/magic-schools', async () => {\n    await request(app)\n      .get('/api/magic-schools')\n      .expect(301)\n      .expect('Location', '/api/2014/magic-schools')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Abjuration'\n    await request(app)\n      .get(`/api/magic-schools?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/magic-schools?name=${name}`)\n  })\n\n  it('redirects to /api/2014/magic-schools/{index}', async () => {\n    const index = 'conjuration'\n    await request(app)\n      .get(`/api/magic-schools/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/magic-schools/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/monsters.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/monsters', () => {\n  it('redirects to /api/2014/monsters', async () => {\n    await request(app).get('/api/monsters').expect(301).expect('Location', '/api/2014/monsters')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Aboleth'\n    await request(app)\n      .get(`/api/monsters?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/monsters?name=${name}`)\n  })\n\n  it('redirects to /api/2014/monsters/{index}', async () => {\n    const index = 'acolyte'\n    await request(app)\n      .get(`/api/monsters/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/monsters/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/proficiencies.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/proficiencies', () => {\n  it('redirects to /api/2014/proficiencies', async () => {\n    await request(app)\n      .get('/api/proficiencies')\n      .expect(301)\n      .expect('Location', '/api/2014/proficiencies')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Bagpipes'\n    await request(app)\n      .get(`/api/proficiencies?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/proficiencies?name=${name}`)\n  })\n\n  it('redirects to /api/2014/proficiencies/{index}', async () => {\n    const index = 'blowguns'\n    await request(app)\n      .get(`/api/proficiencies/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/proficiencies/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/races.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/races', () => {\n  it('redirects to /api/2014/races', async () => {\n    await request(app).get('/api/races').expect(301).expect('Location', '/api/2014/races')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Dragonborn'\n    await request(app)\n      .get(`/api/races?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/races?name=${name}`)\n  })\n\n  it('redirects to /api/2014/races/{index}', async () => {\n    const index = 'dwarf'\n    await request(app)\n      .get(`/api/races/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/races/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/ruleSections.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/rule-sections', () => {\n  it('redirects to /api/2014/rule-sections', async () => {\n    await request(app)\n      .get('/api/rule-sections')\n      .expect(301)\n      .expect('Location', '/api/2014/rule-sections')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Ability%20Checks'\n    await request(app)\n      .get(`/api/rule-sections?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/rule-sections?name=${name}`)\n  })\n\n  it('redirects to /api/2014/rule-sections/{index}', async () => {\n    const index = 'actions-in-combat'\n    await request(app)\n      .get(`/api/rule-sections/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/rule-sections/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/rules.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/rules', () => {\n  it('redirects to /api/2014/rules', async () => {\n    await request(app).get('/api/rules').expect(301).expect('Location', '/api/2014/rules')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Adventuring'\n    await request(app)\n      .get(`/api/rules?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/rules?name=${name}`)\n  })\n\n  it('redirects to /api/2014/rules/{index}', async () => {\n    const index = 'appendix'\n    await request(app)\n      .get(`/api/rules/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/rules/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/skills.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/skills', () => {\n  it('redirects to /api/2014/skills', async () => {\n    await request(app).get('/api/skills').expect(301).expect('Location', '/api/2014/skills')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Acrobatics'\n    await request(app)\n      .get(`/api/skills?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/skills?name=${name}`)\n  })\n\n  it('redirects to /api/2014/skills/{index}', async () => {\n    const index = 'arcana'\n    await request(app)\n      .get(`/api/skills/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/skills/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/spells.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/spells', () => {\n  it('redirects to /api/2014/spells', async () => {\n    await request(app).get('/api/spells').expect(301).expect('Location', '/api/2014/spells')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Acid%20Arrow'\n    await request(app)\n      .get(`/api/spells?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/spells?name=${name}`)\n  })\n\n  it('redirects to /api/2014/spells/{index}', async () => {\n    const index = 'aid'\n    await request(app)\n      .get(`/api/spells/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/spells/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/subclasses.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/subclasses', () => {\n  it('redirects to /api/2014/subclasses', async () => {\n    await request(app).get('/api/subclasses').expect(301).expect('Location', '/api/2014/subclasses')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Berserker'\n    await request(app)\n      .get(`/api/subclasses?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/subclasses?name=${name}`)\n  })\n\n  it('redirects to /api/2014/subclasses/{index}', async () => {\n    const index = 'Champion'\n    await request(app)\n      .get(`/api/subclasses/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/subclasses/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/subraces.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/subraces', () => {\n  it('redirects to /api/2014/subraces', async () => {\n    await request(app).get('/api/subraces').expect(301).expect('Location', '/api/2014/subraces')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'High%20Elf'\n    await request(app)\n      .get(`/api/subraces?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/subraces?name=${name}`)\n  })\n\n  it('redirects to /api/2014/subraces/{index}', async () => {\n    const index = 'hill-dwarf'\n    await request(app)\n      .get(`/api/subraces/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/subraces/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/traits.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/traits', () => {\n  it('redirects to /api/2014/traits', async () => {\n    await request(app).get('/api/traits').expect(301).expect('Location', '/api/2014/traits')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Brave'\n    await request(app)\n      .get(`/api/traits?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/traits?name=${name}`)\n  })\n\n  it('redirects to /api/2014/traits/{index}', async () => {\n    const index = 'darkvision'\n    await request(app)\n      .get(`/api/traits/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/traits/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/api/weaponProperties.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/api/weapon-properties', () => {\n  it('redirects to /api/2014/weapon-properties', async () => {\n    await request(app)\n      .get('/api/weapon-properties')\n      .expect(301)\n      .expect('Location', '/api/2014/weapon-properties')\n  })\n\n  it('redirects preserving query parameters', async () => {\n    const name = 'Ammunition'\n    await request(app)\n      .get(`/api/weapon-properties?name=${name}`)\n      .expect(301)\n      .expect('Location', `/api/2014/weapon-properties?name=${name}`)\n  })\n\n  it('redirects to /api/2014/weapon-properties/{index}', async () => {\n    const index = 'finesse'\n    await request(app)\n      .get(`/api/weapon-properties/${index}`)\n      .expect(301)\n      .expect('Location', `/api/2014/weapon-properties/${index}`)\n  })\n})\n"
  },
  {
    "path": "src/tests/integration/server.itest.ts",
    "content": "import { Application } from 'express'\nimport mongoose from 'mongoose'\nimport request from 'supertest'\nimport { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'\n\nimport createApp from '@/server'\nimport { mongodbUri, redisClient } from '@/util'\n\nlet app: Application\nlet server: any\n\nafterEach(() => {\n  vi.clearAllMocks()\n})\n\nbeforeAll(async () => {\n  await mongoose.connect(mongodbUri)\n  await redisClient.connect()\n  app = await createApp()\n  server = app.listen() // Start the server and store the instance\n})\n\nafterAll(async () => {\n  await mongoose.disconnect()\n  await redisClient.quit()\n  server.close()\n})\n\ndescribe('/', () => {\n  it('should load the page', async () => {\n    const res = await request(app).get('/')\n    expect(res.statusCode).toEqual(200)\n  })\n})\n\ndescribe('/docs', () => {\n  it('should redirect', async () => {\n    const res = await request(app).get('/docs')\n    expect(res.statusCode).toEqual(302)\n  })\n})\n\ndescribe('/bad-url', () => {\n  it('404s', async () => {\n    const res = await request(app).get('/bad-url')\n    expect(res.statusCode).toEqual(404)\n  })\n})\n\ndescribe('/api', () => {\n  it('should redirect to /api/2014', async () => {\n    await request(app).get('/api').expect(301).expect('Location', '/api/2014/')\n  })\n})\n\ndescribe('/api/2014', () => {\n  it('should list the endpoints', async () => {\n    const res = await request(app).get('/api/2014')\n    expect(res.statusCode).toEqual(200)\n    expect(res.body).toHaveProperty('ability-scores')\n    expect(res.body).not.toHaveProperty('levels')\n  })\n})\n"
  },
  {
    "path": "src/tests/support/db.ts",
    "content": "import crypto from 'crypto'\n\nimport mongoose, { type Model } from 'mongoose'\nimport { afterAll, beforeAll, beforeEach, vi } from 'vitest'\n\n/**\n * Generates a unique MongoDB URI for test isolation.\n * Requires the TEST_MONGODB_URI_BASE environment variable to be set.\n * @param baseName - A descriptive name for the test suite (e.g., 'abilityscore').\n * @returns A unique MongoDB URI string.\n */\nexport function generateUniqueDbUri(baseName: string): string {\n  const baseUri = process.env.TEST_MONGODB_URI_BASE\n  if (baseUri === undefined) {\n    throw new Error(\n      'TEST_MONGODB_URI_BASE environment variable not set. Ensure global setup providing this variable ran before tests.'\n    )\n  }\n  // Ensure baseName is filesystem-friendly if used directly in DB name\n  const safeBaseName = baseName.replace(/[^a-zA-Z0-9]/g, '_')\n  const uniqueSuffix = crypto.randomBytes(4).toString('hex')\n  const dbName = `test_${safeBaseName}_${uniqueSuffix}`\n  return `${baseUri}${dbName}` // Assumes baseUri ends with '/' or is just the host part\n}\n\n/**\n * Registers vitest hooks to connect to a unique, isolated MongoDB database before all tests in a suite.\n * @param uri - The unique MongoDB URI generated by generateUniqueDbUri.\n */\nexport function setupIsolatedDatabase(uri: string): void {\n  beforeAll(async () => {\n    try {\n      await mongoose.connect(uri)\n    } catch (error) {\n      console.error(`Failed to connect to MongoDB at ${uri}`, error)\n      // Re-throw the error to fail the test suite explicitly\n      throw new Error(\n        `Database connection failed: ${error instanceof Error ? error.message : String(error)}`,\n        { cause: error }\n      )\n    }\n  })\n}\n\n/**\n * Registers vitest hooks to drop the database and disconnect Mongoose after all tests in a suite.\n */\nexport function teardownIsolatedDatabase(): void {\n  afterAll(async () => {\n    if (mongoose.connection.readyState === 1) {\n      try {\n        if (mongoose.connection.db) {\n          await mongoose.connection.db.dropDatabase()\n        }\n      } catch (err) {\n        console.error(`Error dropping database ${mongoose.connection.name}:`, err)\n        // Decide if you want to throw here or just log\n      } finally {\n        await mongoose.disconnect()\n      }\n    } else {\n      // Ensure disconnection even if connection failed or was already closed\n      await mongoose.disconnect()\n    }\n  })\n}\n\n/**\n * Registers vitest hooks to clear mocks and delete all documents from a specific model before each test.\n * @param model - The Mongoose model to clean up before each test.\n */\nexport function setupModelCleanup(model: Model<any>): void {\n  beforeEach(async () => {\n    vi.clearAllMocks()\n    try {\n      // Ensure connection is ready before attempting cleanup\n      if (mongoose.connection.readyState === 1) {\n        await model.deleteMany({})\n      } else {\n        console.warn(`Skipping cleanup for ${model.modelName}: Mongoose not connected.`)\n      }\n    } catch (error) {\n      console.error(`Error cleaning up model ${model.modelName}:`, error)\n      // Decide if you want to throw here or just log\n      throw new Error(\n        `Model cleanup failed: ${error instanceof Error ? error.message : String(error)}`,\n        { cause: error }\n      )\n    }\n  })\n}\n"
  },
  {
    "path": "src/tests/support/index.ts",
    "content": "export { mockNext } from './requestHelpers'\n"
  },
  {
    "path": "src/tests/support/requestHelpers.ts",
    "content": "import { vi } from 'vitest'\n\nexport const mockNext = vi.fn()\n"
  },
  {
    "path": "src/tests/support/types.d.ts",
    "content": "import { vi } from 'vitest'\n\nexport type MockResponse = {\n  status?: vi.Mock<any, any>\n  json?: vi.Mock<any, any>\n}\n"
  },
  {
    "path": "src/tests/util/data.test.ts",
    "content": "import { describe, expect, it } from 'vitest'\n\nimport { ResourceList } from '@/util/data'\n\ndescribe('ResourceList', () => {\n  it('returns a constructed hash from list', () => {\n    const data = [\n      { index: 'test1', name: 'Test 1', url: '/made/up/url/test1' },\n      { index: 'test2', name: 'Test 2', url: '/made/up/url/test2' },\n      { index: 'test2', name: 'Test 2', url: '/made/up/url/test2' }\n    ]\n    const resource = ResourceList(data)\n    expect(resource.count).toEqual(data.length)\n    expect(resource.results).toEqual(data)\n  })\n})\n"
  },
  {
    "path": "src/tests/vitest.setup.ts",
    "content": "import { vi } from 'vitest'\n\n// Mock the entire module that exports redisClient\nvi.mock('@/util', async (importOriginal) => {\n  // Get the actual module contents\n  const actual = await importOriginal<typeof import('@/util')>()\n\n  // Create mock functions for the redisClient methods used in the app\n  const mockRedisClient = {\n    get: vi.fn().mockResolvedValue(null), // Default mock: cache miss\n    set: vi.fn().mockResolvedValue('OK'), // Default mock: successful set\n    del: vi.fn().mockResolvedValue(1), // Default mock: successful delete\n    on: vi.fn(), // Mock for event listener registration\n    // Add common commands used in start.ts or elsewhere\n    // Use flushDb usually, but include flushAll if specifically used\n    flushDb: vi.fn().mockResolvedValue('OK'),\n    flushAll: vi.fn().mockResolvedValue('OK'),\n    // Ensure connect/quit are mocked if used during startup/shutdown logic\n    connect: vi.fn().mockResolvedValue(undefined),\n    quit: vi.fn().mockResolvedValue(undefined),\n    isOpen: true // Mock the state property/getter\n    // Add mocks for any other redisClient methods your application uses\n    // e.g., connect: vi.fn().mockResolvedValue(undefined),\n    // e.g., quit: vi.fn().mockResolvedValue(undefined),\n    // e.g., isOpen: true, // Or a getter mock: get isOpen() { return true; }\n  }\n\n  return {\n    ...actual, // Keep all other exports from the original module\n    redisClient: mockRedisClient // Replace redisClient with our mock\n  }\n})\n\n// You can add other global setup logic here if needed, like clearing mocks\n// afterEach(() => {\n//   vi.clearAllMocks();\n// });\n"
  },
  {
    "path": "src/util/RedisClient.ts",
    "content": "import { createClient } from 'redis'\n\nimport { redisUrl } from './environmentVariables'\n\nconst isTls = redisUrl.match(/rediss:/) != null\nconst parsedUrl = new URL(redisUrl)\n\nexport default createClient({\n  url: redisUrl,\n  socket: isTls\n    ? {\n        tls: true,\n        host: parsedUrl.hostname,\n        rejectUnauthorized: false\n      }\n    : undefined\n})\n"
  },
  {
    "path": "src/util/awsS3Client.ts",
    "content": "import { S3Client, S3ClientConfig } from '@aws-sdk/client-s3'\n\nimport {\n  awsAccessKeyId,\n  awsConfigEnv,\n  awsRegion,\n  awsSecretAccessKey\n} from '@/util/environmentVariables'\n\nconst awsS3ClientConfigs = {\n  prod: {\n    region: awsRegion,\n    credentials: {\n      accessKeyId: awsAccessKeyId,\n      secretAccessKey: awsSecretAccessKey\n    }\n  } as S3ClientConfig,\n  // Assumes localstack is running and s3 is available at http://localhost:4566\n  // with the bucket dnd-5e-api-images created with a folder named 'monsters'\n  // containing image files.\n  localstack_dev: {\n    region: 'us-east-1',\n    credentials: {\n      accessKeyId: 'test',\n      secretAccessKey: 'test'\n    },\n    endpoint: 'http://s3.localhost.localstack.cloud:4566',\n    forcePathStyle: true\n  } as S3ClientConfig\n}\n\nexport default new S3Client(awsS3ClientConfigs[awsConfigEnv as keyof typeof awsS3ClientConfigs])\n"
  },
  {
    "path": "src/util/data.ts",
    "content": "export const ResourceList = (data: any[]) => {\n  return { count: data.length, results: data }\n}\n"
  },
  {
    "path": "src/util/environmentVariables.ts",
    "content": "const isTestEnv = process.env.NODE_ENV === 'test'\n\nconst redisUrl =\n  process.env.HEROKU_REDIS_YELLOW_URL ?? process.env.REDIS_URL ?? 'redis://localhost:6379'\nconst bugsnagApiKey = process.env.BUGSNAG_API_KEY ?? null\nconst mongodbUri =\n  (isTestEnv ? process.env.TEST_MONGODB_URI : process.env.MONGODB_URI) ??\n  'mongodb://localhost/5e-database'\n\nconst awsConfigEnv = process.env.AWS_CONFIG_ENV ?? 'prod'\nconst awsRegion = process.env.AWS_REGION ?? 'us-west-1'\nconst awsAccessKeyId = process.env.AWS_ACCESS_KEY_ID ?? ''\nconst awsSecretAccessKey = process.env.AWS_SECRET_ACCESS_KEY ?? ''\n\nexport {\n  awsAccessKeyId,\n  awsConfigEnv,\n  awsRegion,\n  awsSecretAccessKey,\n  bugsnagApiKey,\n  mongodbUri,\n  redisUrl\n}\n"
  },
  {
    "path": "src/util/index.ts",
    "content": "import awsS3Client from './awsS3Client'\nimport { ResourceList } from './data'\nimport { bugsnagApiKey, mongodbUri } from './environmentVariables'\nimport prewarmCache from './prewarmCache'\nimport redisClient from './RedisClient'\nimport { escapeRegExp } from './regex'\n\nexport {\n  awsS3Client,\n  bugsnagApiKey,\n  escapeRegExp,\n  mongodbUri,\n  prewarmCache,\n  redisClient,\n  ResourceList\n}\n"
  },
  {
    "path": "src/util/modelOptions.ts",
    "content": "import { modelOptions, Severity } from '@typegoose/typegoose'\n\n/**\n * Helper function to recursively remove _id and __v fields.\n * @param obj The object or array to process.\n */\nfunction removeInternalFields(obj: any): any {\n  if (Array.isArray(obj)) {\n    return obj.map(removeInternalFields)\n  } else if (obj !== null && typeof obj === 'object') {\n    // Mongoose documents might have a toObject method, use it if available\n    const plainObject = typeof obj.toObject === 'function' ? obj.toObject() : obj\n\n    // Create a new object to avoid modifying the original Mongoose document directly if necessary\n    const newObj: any = {}\n    for (const key in plainObject) {\n      if (key !== '_id' && key !== '__v' && key !== 'id') {\n        newObj[key] = removeInternalFields(plainObject[key])\n      }\n    }\n    return newObj\n  }\n  return obj // Return primitives/unhandled types as is\n}\n\n/**\n * Creates common Typegoose model options for SRD API models.\n * - Sets the collection name.\n * - Disables the default _id field.\n * - Enables timestamps (createdAt, updatedAt).\n * - Configures toJSON and toObject transforms to remove _id and __v recursively.\n *\n * @param collectionName The name of the MongoDB collection.\n * @returns A Typegoose ClassDecorator.\n */\nexport function srdModelOptions(collectionName: string): ClassDecorator {\n  return modelOptions({\n    // It's often good practice to explicitly set allowMixed\n    options: { allowMixed: Severity.ALLOW },\n    schemaOptions: {\n      collection: collectionName,\n      // Prevent Mongoose from managing the _id field directly\n      _id: false,\n      // Automatically add createdAt and updatedAt timestamps\n      timestamps: true,\n      // Modify the object when converting to JSON (e.g., for API responses)\n      toJSON: {\n        virtuals: true, // Ensure virtuals are included if you add any\n        transform: (doc, ret) => {\n          return removeInternalFields(ret)\n        }\n      },\n      // Also apply the same transform when converting to a plain object\n      toObject: {\n        virtuals: true,\n        transform: (doc, ret) => {\n          return removeInternalFields(ret)\n        }\n      }\n    }\n  })\n}\n"
  },
  {
    "path": "src/util/prewarmCache.ts",
    "content": "import { getModelForClass } from '@typegoose/typegoose'\n\nimport MagicItem from '@/models/2014/magicItem'\nimport Monster from '@/models/2014/monster'\nimport Rule from '@/models/2014/rule'\nimport RuleSection from '@/models/2014/ruleSection'\nimport Spell from '@/models/2014/spell'\n\nimport { ResourceList } from './data'\nimport redisClient from './RedisClient'\n\ntype PrewarmData = {\n  Schema: ReturnType<typeof getModelForClass>\n  endpoint: string\n}\n\nconst prewarmCache = async () => {\n  const toPrewarm: PrewarmData[] = [\n    {\n      Schema: MagicItem,\n      endpoint: '/api/2014/magic-items'\n    },\n    {\n      Schema: Spell,\n      endpoint: '/api/2014/spells'\n    },\n    {\n      Schema: Monster,\n      endpoint: '/api/2014/monsters'\n    },\n    {\n      Schema: Rule,\n      endpoint: '/api/2014/rules'\n    },\n    {\n      Schema: RuleSection,\n      endpoint: '/api/2014/rule-sections'\n    }\n  ]\n  for (const element of toPrewarm) {\n    const data = await element.Schema.find()\n      .select({ index: 1, level: 1, name: 1, url: 1, _id: 0 })\n      .sort({ index: 'asc' })\n    const jsonData = ResourceList(data)\n    await redisClient.set(element.endpoint, JSON.stringify(jsonData))\n  }\n}\n\nexport default prewarmCache\n"
  },
  {
    "path": "src/util/regex.ts",
    "content": "export const escapeRegExp = (string: string) => {\n  return string.toString().replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&') // $& means the whole matched string\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowJs\": true,\n    \"outDir\": \"./dist\",\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"baseUrl\": \".\",\n    \"experimentalDecorators\": true,\n    \"emitDecoratorMetadata\": true,\n    \"paths\": {\n      \"@/*\": [\"src/*\"]\n    }\n  },\n  \"include\": [\n    \"src/js/**/*.js\",\n    \"src/**/*.ts\",\n    \"tools/**/*.ts\",\n    \"vitest.config.ts\",\n    \"vitest.config.integration.ts\",\n    \"eslint.config.js\"\n  ],\n  \"exclude\": [\"node_modules\"],\n  \"ts-node\": {\n    \"esm\": true\n  },\n  \"typedocOptions\": {\n    \"entryPoints\": [\"src/models/index.ts\"],\n    \"out\": \"docs\"\n  },\n  \"tsc-alias\": {\n    \"resolveFullPaths\": true\n  }\n}\n"
  },
  {
    "path": "vitest.config.integration.ts",
    "content": "import path from 'path'\n\nimport { defineConfig } from 'vitest/config'\n\nexport default defineConfig({\n  test: {\n    // Specify options specific to integration tests\n    globals: true, // Keep or remove based on preference\n    environment: 'node',\n    include: ['src/tests/integration/**/*.itest.ts'],\n    // Consider longer timeouts for integration tests involving DB/network/server startup\n    testTimeout: 20000,\n    hookTimeout: 30000 // Timeout for globalSetup/teardown\n    // Any other specific integration test settings...\n  },\n  // Copy essential options from base config, like resolve.alias\n  resolve: {\n    alias: {\n      '@': path.resolve(__dirname, './src')\n    }\n  }\n})\n"
  },
  {
    "path": "vitest.config.ts",
    "content": "import path from 'path'\n\nimport { defineConfig } from 'vitest/config'\n\nexport default defineConfig({\n  test: {\n    globals: true, // Optional: Use Vitest global APIs (describe, it, etc.) without importing\n    environment: 'node', // Specify Node.js environment\n    globalSetup: ['./src/tests/controllers/globalSetup.ts'], // Path to the global setup file\n    // maxConcurrency: 1, // Removed - Use DB isolation for parallelism\n    // Optional: Increase timeouts if global setup takes longer\n    setupFiles: ['./src/tests/vitest.setup.ts'],\n    // testTimeout: 30000,\n    // hookTimeout: 30000,\n    deps: {\n      // Remove optimizer settings related to factory-js\n      optimizer: {\n        ssr: {\n          include: [] // Keep the structure but empty the array\n        }\n      }\n    }\n  },\n  resolve: {\n    alias: {\n      '@': path.resolve(__dirname, './src')\n    }\n  }\n})\n"
  }
]